View Javadoc
1   /*
2    *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: DefaultModelContext.java 5391 2016-09-30 21:25:10Z schulte $
29   *
30   */
31  package org.jomc.modlet;
32  
33  import java.io.BufferedReader;
34  import java.io.File;
35  import java.io.FileInputStream;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.InputStreamReader;
39  import java.io.Reader;
40  import java.lang.reflect.UndeclaredThrowableException;
41  import java.net.URI;
42  import java.net.URISyntaxException;
43  import java.net.URL;
44  import java.text.MessageFormat;
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Collections;
48  import java.util.Comparator;
49  import java.util.Enumeration;
50  import java.util.HashSet;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.ResourceBundle;
55  import java.util.Set;
56  import java.util.StringTokenizer;
57  import java.util.TreeMap;
58  import java.util.concurrent.Callable;
59  import java.util.concurrent.CancellationException;
60  import java.util.concurrent.CopyOnWriteArrayList;
61  import java.util.concurrent.ExecutionException;
62  import java.util.concurrent.Future;
63  import java.util.jar.Attributes;
64  import java.util.jar.Manifest;
65  import java.util.logging.Level;
66  import javax.xml.XMLConstants;
67  import javax.xml.bind.JAXBContext;
68  import javax.xml.bind.JAXBException;
69  import javax.xml.bind.Marshaller;
70  import javax.xml.bind.Unmarshaller;
71  import javax.xml.transform.Source;
72  import javax.xml.transform.sax.SAXSource;
73  import javax.xml.validation.SchemaFactory;
74  import javax.xml.validation.Validator;
75  import org.w3c.dom.ls.LSInput;
76  import org.w3c.dom.ls.LSResourceResolver;
77  import org.xml.sax.EntityResolver;
78  import org.xml.sax.ErrorHandler;
79  import org.xml.sax.InputSource;
80  import org.xml.sax.SAXException;
81  import org.xml.sax.SAXParseException;
82  import org.xml.sax.helpers.DefaultHandler;
83  
84  /**
85   * Default {@code ModelContext} implementation.
86   *
87   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
88   * @version $JOMC: DefaultModelContext.java 5391 2016-09-30 21:25:10Z schulte $
89   * @see ModelContextFactory
90   */
91  public class DefaultModelContext extends ModelContext
92  {
93  
94      /**
95       * Constant for the name of the model context attribute backing property {@code providerLocation}.
96       *
97       * @see #getProviderLocation()
98       * @see ModelContext#getAttribute(java.lang.String)
99       * @since 1.2
100      */
101     public static final String PROVIDER_LOCATION_ATTRIBUTE_NAME =
102         "org.jomc.modlet.DefaultModelContext.providerLocationAttribute";
103 
104     /**
105      * Constant for the name of the model context attribute backing property {@code platformProviderLocation}.
106      *
107      * @see #getPlatformProviderLocation()
108      * @see ModelContext#getAttribute(java.lang.String)
109      * @since 1.2
110      */
111     public static final String PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME =
112         "org.jomc.modlet.DefaultModelContext.platformProviderLocationAttribute";
113 
114     /**
115      * Supported schema name extensions.
116      */
117     private static final String[] SCHEMA_EXTENSIONS = new String[]
118     {
119         "xsd"
120     };
121 
122     /**
123      * Class path location searched for providers by default.
124      *
125      * @see #getDefaultProviderLocation()
126      */
127     private static final String DEFAULT_PROVIDER_LOCATION = "META-INF/services";
128 
129     /**
130      * Location searched for platform providers by default.
131      *
132      * @see #getDefaultPlatformProviderLocation()
133      */
134     private static final String DEFAULT_PLATFORM_PROVIDER_LOCATION =
135         new StringBuilder( 255 ).append( System.getProperty( "java.home" ) ).append( File.separator ).append( "lib" ).
136         append( File.separator ).append( "jomc.properties" ).toString();
137 
138     /**
139      * Constant for the service identifier of marshaller listener services.
140      *
141      * @since 1.2
142      */
143     private static final String MARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Marshaller.Listener";
144 
145     /**
146      * Constant for the service identifier of unmarshaller listener services.
147      *
148      * @since 1.2
149      */
150     private static final String UNMARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Unmarshaller.Listener";
151 
152     /**
153      * Default provider location.
154      */
155     private static volatile String defaultProviderLocation;
156 
157     /**
158      * Default platform provider location.
159      */
160     private static volatile String defaultPlatformProviderLocation;
161 
162     /**
163      * Provider location of the instance.
164      */
165     private volatile String providerLocation;
166 
167     /**
168      * Platform provider location of the instance.
169      */
170     private volatile String platformProviderLocation;
171 
172     /**
173      * Creates a new {@code DefaultModelContext} instance.
174      *
175      * @since 1.2
176      */
177     public DefaultModelContext()
178     {
179         super();
180     }
181 
182     /**
183      * Creates a new {@code DefaultModelContext} instance taking a class loader.
184      *
185      * @param classLoader The class loader of the context.
186      */
187     public DefaultModelContext( final ClassLoader classLoader )
188     {
189         super( classLoader );
190     }
191 
192     /**
193      * Gets the default location searched for provider resources.
194      * <p>
195      * The default provider location is controlled by system property
196      * {@code org.jomc.modlet.DefaultModelContext.defaultProviderLocation} holding the location to search
197      * for provider resources by default. If that property is not set, the {@code META-INF/services} default is
198      * returned.
199      * </p>
200      *
201      * @return The location searched for provider resources by default.
202      *
203      * @see #setDefaultProviderLocation(java.lang.String)
204      */
205     public static String getDefaultProviderLocation()
206     {
207         if ( defaultProviderLocation == null )
208         {
209             defaultProviderLocation = System.getProperty(
210                 "org.jomc.modlet.DefaultModelContext.defaultProviderLocation", DEFAULT_PROVIDER_LOCATION );
211 
212         }
213 
214         return defaultProviderLocation;
215     }
216 
217     /**
218      * Sets the default location searched for provider resources.
219      *
220      * @param value The new default location to search for provider resources or {@code null}.
221      *
222      * @see #getDefaultProviderLocation()
223      */
224     public static void setDefaultProviderLocation( final String value )
225     {
226         defaultProviderLocation = value;
227     }
228 
229     /**
230      * Gets the location searched for provider resources.
231      *
232      * @return The location searched for provider resources.
233      *
234      * @see #getDefaultProviderLocation()
235      * @see #setProviderLocation(java.lang.String)
236      * @see #PROVIDER_LOCATION_ATTRIBUTE_NAME
237      */
238     public final String getProviderLocation()
239     {
240         String location = this.providerLocation;
241 
242         if ( this.providerLocation == null )
243         {
244             this.providerLocation = getDefaultProviderLocation();
245             location = this.providerLocation;
246 
247             if ( this.isLoggable( Level.CONFIG ) )
248             {
249                 this.log( Level.CONFIG, getMessage( "defaultProviderLocationInfo", location ), null );
250             }
251         }
252 
253         if ( this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
254         {
255             location = (String) this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME );
256 
257             if ( this.isLoggable( Level.CONFIG ) )
258             {
259                 this.log( Level.CONFIG, getMessage( "contextProviderLocationInfo", location ), null );
260             }
261         }
262 
263         return location;
264     }
265 
266     /**
267      * Sets the location searched for provider resources.
268      *
269      * @param value The new location to search for provider resources or {@code null}.
270      *
271      * @see #getProviderLocation()
272      */
273     public final void setProviderLocation( final String value )
274     {
275         this.providerLocation = value;
276     }
277 
278     /**
279      * Gets the default location searched for platform provider resources.
280      * <p>
281      * The default platform provider location is controlled by system property
282      * {@code org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation} holding the location to
283      * search for platform provider resources by default. If that property is not set, the
284      * {@code <java-home>/lib/jomc.properties} default is returned.
285      * </p>
286      *
287      * @return The location searched for platform provider resources by default.
288      *
289      * @see #setDefaultPlatformProviderLocation(java.lang.String)
290      */
291     public static String getDefaultPlatformProviderLocation()
292     {
293         if ( defaultPlatformProviderLocation == null )
294         {
295             defaultPlatformProviderLocation = System.getProperty(
296                 "org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation",
297                 DEFAULT_PLATFORM_PROVIDER_LOCATION );
298 
299         }
300 
301         return defaultPlatformProviderLocation;
302     }
303 
304     /**
305      * Sets the default location searched for platform provider resources.
306      *
307      * @param value The new default location to search for platform provider resources or {@code null}.
308      *
309      * @see #getDefaultPlatformProviderLocation()
310      */
311     public static void setDefaultPlatformProviderLocation( final String value )
312     {
313         defaultPlatformProviderLocation = value;
314     }
315 
316     /**
317      * Gets the location searched for platform provider resources.
318      *
319      * @return The location searched for platform provider resources.
320      *
321      * @see #getDefaultPlatformProviderLocation()
322      * @see #setPlatformProviderLocation(java.lang.String)
323      * @see #PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME
324      */
325     public final String getPlatformProviderLocation()
326     {
327         String location = this.platformProviderLocation;
328 
329         if ( this.platformProviderLocation == null )
330         {
331             this.platformProviderLocation = getDefaultPlatformProviderLocation();
332             location = this.platformProviderLocation;
333 
334             if ( this.isLoggable( Level.CONFIG ) )
335             {
336                 this.log( Level.CONFIG, getMessage( "defaultPlatformProviderLocationInfo", location ), null );
337             }
338         }
339 
340         if ( this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
341         {
342             location = (String) this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME );
343 
344             if ( this.isLoggable( Level.CONFIG ) )
345             {
346                 this.log( Level.CONFIG, getMessage( "contextPlatformProviderLocationInfo", location ), null );
347             }
348         }
349 
350         return location;
351     }
352 
353     /**
354      * Sets the location searched for platform provider resources.
355      *
356      * @param value The new location to search for platform provider resources or {@code null}.
357      *
358      * @see #getPlatformProviderLocation()
359      */
360     public final void setPlatformProviderLocation( final String value )
361     {
362         this.platformProviderLocation = value;
363     }
364 
365     /**
366      * {@inheritDoc}
367      * <p>
368      * This method loads {@code ModletProvider} classes setup via the platform provider configuration file and
369      * {@code <provider-location>/org.jomc.modlet.ModletProvider} resources to return a list of {@code Modlets}.
370      * </p>
371      *
372      * @see #getProviderLocation()
373      * @see #getPlatformProviderLocation()
374      * @see ModletProvider#findModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
375      * @deprecated As of JOMC 1.6, replaced by {@link #findModlets(org.jomc.modlet.Modlets)}. This method will be
376      * removed in JOMC 2.0.
377      */
378     @Override
379     @Deprecated
380     public Modlets findModlets() throws ModelException
381     {
382         return this.findModlets( new Modlets() );
383     }
384 
385     /**
386      * {@inheritDoc}
387      * <p>
388      * This method loads {@code ModletProvider} classes setup via the platform provider configuration file and
389      * {@code <provider-location>/org.jomc.modlet.ModletProvider} resources to return a list of {@code Modlets}.
390      * </p>
391      *
392      * @see #getProviderLocation()
393      * @see #getPlatformProviderLocation()
394      * @see ModletProvider#findModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
395      * @since 1.6
396      */
397     @Override
398     public Modlets findModlets( final Modlets modlets ) throws ModelException
399     {
400         if ( modlets == null )
401         {
402             throw new NullPointerException( "modlets" );
403         }
404 
405         Modlets found = modlets.clone();
406         final Collection<ModletProvider> providers = this.loadModletServices( ModletProvider.class );
407 
408         for ( final ModletProvider provider : providers )
409         {
410             if ( this.isLoggable( Level.FINER ) )
411             {
412                 this.log( Level.FINER, getMessage( "creatingModlets", provider.toString() ), null );
413             }
414 
415             final Modlets provided = provider.findModlets( this, found );
416 
417             if ( provided != null )
418             {
419                 found = provided;
420             }
421         }
422 
423         if ( this.isLoggable( Level.FINEST ) )
424         {
425             for ( final Modlet m : found.getModlet() )
426             {
427                 this.log( Level.FINEST,
428                           getMessage( "modletInfo", m.getName(), m.getModel(),
429                                       m.getVendor() != null
430                                           ? m.getVendor() : getMessage( "noVendor" ),
431                                       m.getVersion() != null
432                                           ? m.getVersion() : getMessage( "noVersion" ) ), null );
433 
434                 if ( m.getSchemas() != null )
435                 {
436                     for ( final Schema s : m.getSchemas().getSchema() )
437                     {
438                         this.log( Level.FINEST,
439                                   getMessage( "modletSchemaInfo", m.getName(), s.getPublicId(), s.getSystemId(),
440                                               s.getContextId() != null
441                                                   ? s.getContextId() : getMessage( "noContext" ),
442                                               s.getClasspathId() != null
443                                                   ? s.getClasspathId() : getMessage( "noClasspathId" ) ), null );
444 
445                     }
446                 }
447 
448                 if ( m.getServices() != null )
449                 {
450                     for ( final Service s : m.getServices().getService() )
451                     {
452                         this.log( Level.FINEST, getMessage( "modletServiceInfo", m.getName(), s.getOrdinal(),
453                                                             s.getIdentifier(), s.getClazz() ), null );
454 
455                     }
456                 }
457             }
458         }
459 
460         return found;
461     }
462 
463     /**
464      * {@inheritDoc}
465      * <p>
466      * This method loads {@code ModletProcessor} classes setup via the platform provider configuration file and
467      * {@code <provider-location>/org.jomc.modlet.ModletProcessor} resources to process a list of {@code Modlets}.
468      * </p>
469      *
470      * @see #getProviderLocation()
471      * @see #getPlatformProviderLocation()
472      * @see ModletProcessor#processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
473      * @since 1.6
474      */
475     @Override
476     public Modlets processModlets( final Modlets modlets ) throws ModelException
477     {
478         if ( modlets == null )
479         {
480             throw new NullPointerException( "modlets" );
481         }
482 
483         Modlets result = modlets.clone();
484         final Collection<ModletProcessor> processors = this.loadModletServices( ModletProcessor.class );
485 
486         for ( final ModletProcessor processor : processors )
487         {
488             if ( this.isLoggable( Level.FINER ) )
489             {
490                 this.log( Level.FINER, getMessage( "processingModlets", processor.toString() ), null );
491             }
492 
493             final Modlets processed = processor.processModlets( this, result );
494 
495             if ( processed != null )
496             {
497                 result = processed;
498             }
499         }
500 
501         return result;
502     }
503 
504     /**
505      * {@inheritDoc}
506      * <p>
507      * This method loads {@code ModletValidator} classes setup via the platform provider configuration file and
508      * {@code <provider-location>/org.jomc.modlet.ModletValidator} resources to validate a list of {@code Modlets}.
509      * </p>
510      *
511      * @see #getProviderLocation()
512      * @see #getPlatformProviderLocation()
513      * @see ModletValidator#validateModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
514      * @since 1.9
515      */
516     @Override
517     public ModelValidationReport validateModlets( final Modlets modlets ) throws ModelException
518     {
519         if ( modlets == null )
520         {
521             throw new NullPointerException( "modlets" );
522         }
523 
524         final ModelValidationReport report = new ModelValidationReport();
525 
526         for ( final ModletValidator modletValidator
527                   : this.loadModletServices( ModletValidator.class ) )
528         {
529             if ( this.isLoggable( Level.FINER ) )
530             {
531                 this.log( Level.FINER, getMessage( "validatingModlets", modletValidator.toString() ), null );
532             }
533 
534             final ModelValidationReport current = modletValidator.validateModlets( this, modlets );
535 
536             if ( current != null )
537             {
538                 report.getDetails().addAll( current.getDetails() );
539             }
540         }
541 
542         return report;
543     }
544 
545     /**
546      * {@inheritDoc}
547      * <p>
548      * This method creates all {@code ModelProvider} service objects of the model identified by {@code model} to create
549      * a new {@code Model} instance.
550      * </p>
551      *
552      * @see #findModel(org.jomc.modlet.Model)
553      * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProvider.class.getName(), ModelProvider.class )
554      * @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
555      */
556     @Override
557     public Model findModel( final String model ) throws ModelException
558     {
559         if ( model == null )
560         {
561             throw new NullPointerException( "model" );
562         }
563 
564         final Model m = new Model();
565         m.setIdentifier( model );
566 
567         return this.findModel( m );
568     }
569 
570     /**
571      * {@inheritDoc}
572      * <p>
573      * This method creates all {@code ModelProvider} service objects of the given model to populate the given model
574      * instance.
575      * </p>
576      *
577      * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProvider.class.getName(), ModelProvider.class )
578      * @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
579      *
580      * @since 1.2
581      */
582     @Override
583     public Model findModel( final Model model ) throws ModelException
584     {
585         if ( model == null )
586         {
587             throw new NullPointerException( "model" );
588         }
589 
590         Model m = model.clone();
591         final long t0 = System.nanoTime();
592 
593         for ( final ModelProvider provider
594                   : this.createServiceObjects( model.getIdentifier(), ModelProvider.class.getName(),
595                                                ModelProvider.class ) )
596         {
597             if ( this.isLoggable( Level.FINER ) )
598             {
599                 this.log( Level.FINER, getMessage( "creatingModel", m.getIdentifier(), provider.toString() ), null );
600             }
601 
602             final Model provided = provider.findModel( this, m );
603 
604             if ( provided != null )
605             {
606                 m = provided;
607             }
608         }
609 
610         if ( this.isLoggable( Level.FINE ) )
611         {
612             this.log( Level.FINE, getMessage( "findModelReport", m.getIdentifier(), System.nanoTime() - t0 ), null );
613         }
614 
615         return m;
616     }
617 
618     /**
619      * {@inheritDoc}
620      * <p>
621      * This method creates all {@code ModelProcessor} service objects of {@code model} to process the given
622      * {@code Model}.
623      * </p>
624      *
625      * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProcessor.class.getName(), ModelProcessor.class )
626      * @see ModelProcessor#processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
627      */
628     @Override
629     public Model processModel( final Model model ) throws ModelException
630     {
631         if ( model == null )
632         {
633             throw new NullPointerException( "model" );
634         }
635 
636         Model processed = model;
637         final long t0 = System.nanoTime();
638 
639         for ( final ModelProcessor processor
640                   : this.createServiceObjects( model.getIdentifier(), ModelProcessor.class.getName(),
641                                                ModelProcessor.class ) )
642         {
643             if ( this.isLoggable( Level.FINER ) )
644             {
645                 this.log( Level.FINER, getMessage( "processingModel", model.getIdentifier(),
646                                                    processor.toString() ), null );
647 
648             }
649 
650             final Model current = processor.processModel( this, processed );
651 
652             if ( current != null )
653             {
654                 processed = current;
655             }
656         }
657 
658         if ( this.isLoggable( Level.FINE ) )
659         {
660             this.log( Level.FINE, getMessage( "processModelReport", model.getIdentifier(), System.nanoTime() - t0 ),
661                       null );
662 
663         }
664 
665         return processed;
666     }
667 
668     /**
669      * {@inheritDoc}
670      * <p>
671      * This method creates all {@code ModelValidator} service objects of {@code model} to validate the given
672      * {@code Model}.
673      * </p>
674      *
675      * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelValidator.class.getName(), ModelValidator.class )
676      * @see ModelValidator#validateModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
677      */
678     @Override
679     public ModelValidationReport validateModel( final Model model ) throws ModelException
680     {
681         if ( model == null )
682         {
683             throw new NullPointerException( "model" );
684         }
685 
686         try
687         {
688             final long t0 = System.nanoTime();
689             final ModelValidationReport resultReport = new ModelValidationReport();
690 
691             final Collection<? extends ModelValidator> modelValidators =
692                 this.createServiceObjects( model.getIdentifier(), ModelValidator.class.getName(),
693                                            ModelValidator.class );
694 
695             if ( this.getExecutorService() != null && modelValidators.size() > 1 )
696             {
697                 final List<Callable<ModelValidationReport>> tasks =
698                     new ArrayList<Callable<ModelValidationReport>>( modelValidators.size() );
699 
700                 for ( final ModelValidator validator : modelValidators )
701                 {
702                     tasks.add( new Callable<ModelValidationReport>()
703                     {
704 
705                         public ModelValidationReport call() throws ModelException
706                         {
707                             if ( isLoggable( Level.FINER ) )
708                             {
709                                 log( Level.FINER, getMessage( "validatingModel", model.getIdentifier(),
710                                                               validator.toString() ), null );
711 
712                             }
713 
714                             return validator.validateModel( DefaultModelContext.this, model );
715                         }
716 
717                     } );
718 
719                 }
720 
721                 for ( final Future<ModelValidationReport> task : this.getExecutorService().invokeAll( tasks ) )
722                 {
723                     final ModelValidationReport currentReport = task.get();
724 
725                     if ( currentReport != null )
726                     {
727                         resultReport.getDetails().addAll( currentReport.getDetails() );
728                     }
729                 }
730             }
731             else
732             {
733                 for ( final ModelValidator modelValidator : modelValidators )
734                 {
735                     final ModelValidationReport currentReport = modelValidator.validateModel( this, model );
736 
737                     if ( currentReport != null )
738                     {
739                         resultReport.getDetails().addAll( currentReport.getDetails() );
740                     }
741                 }
742             }
743 
744             if ( this.isLoggable( Level.FINE ) )
745             {
746                 this.log( Level.FINE, getMessage( "validateModelReport", model.getIdentifier(),
747                                                   System.nanoTime() - t0 ), null );
748 
749             }
750 
751             return resultReport;
752         }
753         catch ( final CancellationException e )
754         {
755             throw new ModelException( getMessage( "failedValidatingModel", model.getIdentifier() ), e );
756         }
757         catch ( final InterruptedException e )
758         {
759             throw new ModelException( getMessage( "failedValidatingModel", model.getIdentifier() ), e );
760         }
761         catch ( final ExecutionException e )
762         {
763             if ( e.getCause() instanceof ModelException )
764             {
765                 throw (ModelException) e.getCause();
766             }
767             else if ( e.getCause() instanceof RuntimeException )
768             {
769                 // The fork-join framework breaks the exception handling contract of Callable by re-throwing any
770                 // exception caught using a runtime exception.
771                 if ( e.getCause().getCause() instanceof ModelException )
772                 {
773                     throw (ModelException) e.getCause().getCause();
774                 }
775                 else if ( e.getCause().getCause() instanceof RuntimeException )
776                 {
777                     throw (RuntimeException) e.getCause().getCause();
778                 }
779                 else if ( e.getCause().getCause() instanceof Error )
780                 {
781                     throw (Error) e.getCause().getCause();
782                 }
783                 else if ( e.getCause().getCause() instanceof Exception )
784                 {
785                     // Checked exception not declared to be thrown by the Callable's 'call' method.
786                     throw new UndeclaredThrowableException( e.getCause().getCause() );
787                 }
788                 else
789                 {
790                     throw (RuntimeException) e.getCause();
791                 }
792             }
793             else if ( e.getCause() instanceof Error )
794             {
795                 throw (Error) e.getCause();
796             }
797             else
798             {
799                 // Checked exception not declared to be thrown by the Callable's 'call' method.
800                 throw new UndeclaredThrowableException( e.getCause() );
801             }
802         }
803     }
804 
805     /**
806      * {@inheritDoc}
807      *
808      * @see #createSchema(java.lang.String)
809      */
810     @Override
811     public ModelValidationReport validateModel( final String model, final Source source ) throws ModelException
812     {
813         if ( model == null )
814         {
815             throw new NullPointerException( "model" );
816         }
817         if ( source == null )
818         {
819             throw new NullPointerException( "source" );
820         }
821 
822         final long t0 = System.nanoTime();
823         final javax.xml.validation.Schema schema = this.createSchema( model );
824         final Validator validator = schema.newValidator();
825         final ModelErrorHandler modelErrorHandler = new ModelErrorHandler( this );
826         validator.setErrorHandler( modelErrorHandler );
827 
828         try
829         {
830             validator.validate( source );
831         }
832         catch ( final SAXException e )
833         {
834             String message = getMessage( e );
835             if ( message == null && e.getException() != null )
836             {
837                 message = getMessage( e.getException() );
838             }
839 
840             if ( this.isLoggable( Level.FINE ) )
841             {
842                 this.log( Level.FINE, message, e );
843             }
844 
845             if ( modelErrorHandler.getReport().isModelValid() )
846             {
847                 throw new ModelException( message, e );
848             }
849         }
850         catch ( final IOException e )
851         {
852             throw new ModelException( getMessage( e ), e );
853         }
854 
855         if ( this.isLoggable( Level.FINE ) )
856         {
857             this.log( Level.FINE, getMessage( "validateModelReport", model, System.nanoTime() - t0 ), null );
858         }
859 
860         return modelErrorHandler.getReport();
861     }
862 
863     @Override
864     public EntityResolver createEntityResolver( final String model ) throws ModelException
865     {
866         if ( model == null )
867         {
868             throw new NullPointerException( "model" );
869         }
870 
871         return this.createEntityResolver( this.getModlets().getSchemas( model ) );
872     }
873 
874     @Override
875     @Deprecated
876     public EntityResolver createEntityResolver( final URI publicId ) throws ModelException
877     {
878         if ( publicId == null )
879         {
880             throw new NullPointerException( "publicId" );
881         }
882 
883         return this.createEntityResolver( this.getModlets().getSchemas( publicId ) );
884     }
885 
886     @Override
887     public LSResourceResolver createResourceResolver( final String model ) throws ModelException
888     {
889         if ( model == null )
890         {
891             throw new NullPointerException( "model" );
892         }
893 
894         return this.createResourceResolver( this.createEntityResolver( model ) );
895     }
896 
897     @Override
898     @Deprecated
899     public LSResourceResolver createResourceResolver( final URI publicId ) throws ModelException
900     {
901         if ( publicId == null )
902         {
903             throw new NullPointerException( "publicId" );
904         }
905 
906         return this.createResourceResolver( this.createEntityResolver( publicId ) );
907     }
908 
909     @Override
910     public javax.xml.validation.Schema createSchema( final String model ) throws ModelException
911     {
912         if ( model == null )
913         {
914             throw new NullPointerException( "model" );
915         }
916 
917         return this.createSchema( this.getModlets().getSchemas( model ), this.createEntityResolver( model ),
918                                   this.createResourceResolver( model ), model, null );
919 
920     }
921 
922     @Override
923     @Deprecated
924     public javax.xml.validation.Schema createSchema( final URI publicId ) throws ModelException
925     {
926         if ( publicId == null )
927         {
928             throw new NullPointerException( "publicId" );
929         }
930 
931         return this.createSchema( this.getModlets().getSchemas( publicId ), this.createEntityResolver( publicId ),
932                                   this.createResourceResolver( publicId ), null, publicId );
933 
934     }
935 
936     @Override
937     public JAXBContext createContext( final String model ) throws ModelException
938     {
939         if ( model == null )
940         {
941             throw new NullPointerException( "model" );
942         }
943 
944         return this.createContext( this.getModlets().getSchemas( model ), model, null );
945     }
946 
947     @Override
948     @Deprecated
949     public JAXBContext createContext( final URI publicId ) throws ModelException
950     {
951         if ( publicId == null )
952         {
953             throw new NullPointerException( "publicId" );
954         }
955 
956         return this.createContext( this.getModlets().getSchemas( publicId ), null, publicId );
957     }
958 
959     @Override
960     public Marshaller createMarshaller( final String model ) throws ModelException
961     {
962         if ( model == null )
963         {
964             throw new NullPointerException( "model" );
965         }
966 
967         return this.createMarshaller( model, null );
968     }
969 
970     @Override
971     @Deprecated
972     public Marshaller createMarshaller( final URI publicId ) throws ModelException
973     {
974         if ( publicId == null )
975         {
976             throw new NullPointerException( "publicId" );
977         }
978 
979         return this.createMarshaller( null, publicId );
980     }
981 
982     @Override
983     public Unmarshaller createUnmarshaller( final String model ) throws ModelException
984     {
985         if ( model == null )
986         {
987             throw new NullPointerException( "model" );
988         }
989 
990         return this.createUnmarshaller( model, null );
991     }
992 
993     @Override
994     @Deprecated
995     public Unmarshaller createUnmarshaller( final URI publicId ) throws ModelException
996     {
997         if ( publicId == null )
998         {
999             throw new NullPointerException( "publicId" );
1000         }
1001 
1002         return this.createUnmarshaller( null, publicId );
1003     }
1004 
1005     /**
1006      * {@inheritDoc}
1007      * <p>
1008      * This method loads {@code ServiceFactory} classes setup via the platform provider configuration file and
1009      * {@code <provider-location>/org.jomc.modlet.ServiceFactory} resources to create a new service object.
1010      * </p>
1011      *
1012      * @see #getProviderLocation()
1013      * @see #getPlatformProviderLocation()
1014      * @see ServiceFactory#createServiceObject(org.jomc.modlet.ModelContext, org.jomc.modlet.Service, java.lang.Class)
1015      * @since 1.2
1016      * @deprecated As of JOMC 1.9, please use method {@link #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class)}.
1017      * This method will be removed in JOMC 2.0.
1018      */
1019     @Override
1020     @Deprecated
1021     public <T> T createServiceObject( final Service service, final Class<T> type ) throws ModelException
1022     {
1023         if ( service == null )
1024         {
1025             throw new NullPointerException( "service" );
1026         }
1027         if ( type == null )
1028         {
1029             throw new NullPointerException( "type" );
1030         }
1031 
1032         return this.createServiceObject( service, type, this.loadModletServices( ServiceFactory.class ) );
1033     }
1034 
1035     private EntityResolver createEntityResolver( final Schemas schemas )
1036     {
1037         return new DefaultHandler()
1038         {
1039 
1040             @Override
1041             public InputSource resolveEntity( final String publicId, final String systemId )
1042                 throws SAXException, IOException
1043             {
1044                 InputSource schemaSource = null;
1045 
1046                 try
1047                 {
1048                     Schema s = null;
1049 
1050                     if ( schemas != null )
1051                     {
1052                         if ( systemId != null && !"".equals( systemId ) )
1053                         {
1054                             s = schemas.getSchemaBySystemId( systemId );
1055                         }
1056                         else if ( publicId != null )
1057                         {
1058                             s = schemas.getSchemaByPublicId( publicId );
1059                         }
1060                     }
1061 
1062                     if ( s != null )
1063                     {
1064                         schemaSource = new InputSource();
1065                         schemaSource.setPublicId( s.getPublicId() );
1066                         schemaSource.setSystemId( s.getSystemId() );
1067 
1068                         if ( s.getClasspathId() != null )
1069                         {
1070                             final URL resource = findResource( s.getClasspathId() );
1071 
1072                             if ( resource != null )
1073                             {
1074                                 schemaSource.setSystemId( resource.toExternalForm() );
1075                             }
1076                             else if ( isLoggable( Level.WARNING ) )
1077                             {
1078                                 log( Level.WARNING, getMessage( "resourceNotFound", s.getClasspathId() ), null );
1079                             }
1080                         }
1081 
1082                         if ( isLoggable( Level.FINEST ) )
1083                         {
1084                             log( Level.FINEST, getMessage( "resolutionInfo", publicId + ", " + systemId,
1085                                                            schemaSource.getPublicId() + ", "
1086                                                                + schemaSource.getSystemId() ), null );
1087 
1088                         }
1089                     }
1090 
1091                     if ( schemaSource == null && systemId != null && !"".equals( systemId ) )
1092                     {
1093                         final URI systemUri = new URI( systemId );
1094                         String schemaName = systemUri.getPath();
1095 
1096                         if ( schemaName != null )
1097                         {
1098                             final int lastIndexOfSlash = schemaName.lastIndexOf( '/' );
1099                             if ( lastIndexOfSlash != -1 && lastIndexOfSlash < schemaName.length() )
1100                             {
1101                                 schemaName = schemaName.substring( lastIndexOfSlash + 1 );
1102                             }
1103 
1104                             for ( final URI uri : getSchemaResources() )
1105                             {
1106                                 if ( uri.getSchemeSpecificPart() != null
1107                                          && uri.getSchemeSpecificPart().endsWith( schemaName ) )
1108                                 {
1109                                     schemaSource = new InputSource();
1110                                     schemaSource.setPublicId( publicId );
1111                                     schemaSource.setSystemId( uri.toASCIIString() );
1112 
1113                                     if ( isLoggable( Level.FINEST ) )
1114                                     {
1115                                         log( Level.FINEST, getMessage( "resolutionInfo", systemUri.toASCIIString(),
1116                                                                        schemaSource.getSystemId() ), null );
1117 
1118                                     }
1119 
1120                                     break;
1121                                 }
1122                             }
1123                         }
1124                         else
1125                         {
1126                             if ( isLoggable( Level.WARNING ) )
1127                             {
1128                                 log( Level.WARNING, getMessage( "unsupportedIdUri", systemId,
1129                                                                 systemUri.toASCIIString() ), null );
1130 
1131                             }
1132 
1133                             schemaSource = null;
1134                         }
1135                     }
1136                 }
1137                 catch ( final URISyntaxException e )
1138                 {
1139                     if ( isLoggable( Level.WARNING ) )
1140                     {
1141                         log( Level.WARNING, getMessage( "unsupportedIdUri", systemId, getMessage( e ) ), null );
1142                     }
1143 
1144                     schemaSource = null;
1145                 }
1146                 catch ( final ModelException e )
1147                 {
1148                     String message = getMessage( e );
1149                     if ( message == null )
1150                     {
1151                         message = "";
1152                     }
1153                     else if ( message.length() > 0 )
1154                     {
1155                         message = " " + message;
1156                     }
1157 
1158                     String resource = "";
1159                     if ( publicId != null )
1160                     {
1161                         resource = publicId + ", ";
1162                     }
1163                     resource += systemId;
1164 
1165                     // JDK: As of JDK 6, "new IOException( message, cause )".
1166                     throw (IOException) new IOException( getMessage(
1167                         "failedResolving", resource, message ) ).initCause( e );
1168 
1169                 }
1170 
1171                 return schemaSource;
1172             }
1173 
1174         };
1175     }
1176 
1177     private LSResourceResolver createResourceResolver( final EntityResolver entityResolver )
1178     {
1179         if ( entityResolver == null )
1180         {
1181             throw new NullPointerException( "entityResolver" );
1182         }
1183 
1184         return new LSResourceResolver()
1185         {
1186 
1187             public LSInput resolveResource( final String type, final String namespaceURI, final String publicId,
1188                                             final String systemId, final String baseURI )
1189             {
1190                 final String resolvePublicId = namespaceURI == null ? publicId : namespaceURI;
1191                 final String resolveSystemId = systemId == null ? "" : systemId;
1192 
1193                 try
1194                 {
1195                     if ( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) )
1196                     {
1197                         final InputSource schemaSource =
1198                             entityResolver.resolveEntity( resolvePublicId, resolveSystemId );
1199 
1200                         if ( schemaSource != null )
1201                         {
1202                             return new LSInput()
1203                             {
1204 
1205                                 public Reader getCharacterStream()
1206                                 {
1207                                     return schemaSource.getCharacterStream();
1208                                 }
1209 
1210                                 public void setCharacterStream( final Reader characterStream )
1211                                 {
1212                                     if ( isLoggable( Level.WARNING ) )
1213                                     {
1214                                         log( Level.WARNING, getMessage(
1215                                              "unsupportedOperation", "setCharacterStream",
1216                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1217 
1218                                     }
1219                                 }
1220 
1221                                 public InputStream getByteStream()
1222                                 {
1223                                     return schemaSource.getByteStream();
1224                                 }
1225 
1226                                 public void setByteStream( final InputStream byteStream )
1227                                 {
1228                                     if ( isLoggable( Level.WARNING ) )
1229                                     {
1230                                         log( Level.WARNING, getMessage(
1231                                              "unsupportedOperation", "setByteStream",
1232                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1233 
1234                                     }
1235                                 }
1236 
1237                                 public String getStringData()
1238                                 {
1239                                     return null;
1240                                 }
1241 
1242                                 public void setStringData( final String stringData )
1243                                 {
1244                                     if ( isLoggable( Level.WARNING ) )
1245                                     {
1246                                         log( Level.WARNING, getMessage(
1247                                              "unsupportedOperation", "setStringData",
1248                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1249 
1250                                     }
1251                                 }
1252 
1253                                 public String getSystemId()
1254                                 {
1255                                     return schemaSource.getSystemId();
1256                                 }
1257 
1258                                 public void setSystemId( final String systemId )
1259                                 {
1260                                     if ( isLoggable( Level.WARNING ) )
1261                                     {
1262                                         log( Level.WARNING, getMessage(
1263                                              "unsupportedOperation", "setSystemId",
1264                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1265 
1266                                     }
1267                                 }
1268 
1269                                 public String getPublicId()
1270                                 {
1271                                     return schemaSource.getPublicId();
1272                                 }
1273 
1274                                 public void setPublicId( final String publicId )
1275                                 {
1276                                     if ( isLoggable( Level.WARNING ) )
1277                                     {
1278                                         log( Level.WARNING, getMessage(
1279                                              "unsupportedOperation", "setPublicId",
1280                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1281 
1282                                     }
1283                                 }
1284 
1285                                 public String getBaseURI()
1286                                 {
1287                                     return baseURI;
1288                                 }
1289 
1290                                 public void setBaseURI( final String baseURI )
1291                                 {
1292                                     if ( isLoggable( Level.WARNING ) )
1293                                     {
1294                                         log( Level.WARNING, getMessage(
1295                                              "unsupportedOperation", "setBaseURI",
1296                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1297 
1298                                     }
1299                                 }
1300 
1301                                 public String getEncoding()
1302                                 {
1303                                     return schemaSource.getEncoding();
1304                                 }
1305 
1306                                 public void setEncoding( final String encoding )
1307                                 {
1308                                     if ( isLoggable( Level.WARNING ) )
1309                                     {
1310                                         log( Level.WARNING, getMessage(
1311                                              "unsupportedOperation", "setEncoding",
1312                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1313 
1314                                     }
1315                                 }
1316 
1317                                 public boolean getCertifiedText()
1318                                 {
1319                                     return false;
1320                                 }
1321 
1322                                 public void setCertifiedText( final boolean certifiedText )
1323                                 {
1324                                     if ( isLoggable( Level.WARNING ) )
1325                                     {
1326                                         log( Level.WARNING, getMessage(
1327                                              "unsupportedOperation", "setCertifiedText",
1328                                              DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1329 
1330                                     }
1331                                 }
1332 
1333                             };
1334                         }
1335 
1336                     }
1337                     else if ( isLoggable( Level.WARNING ) )
1338                     {
1339                         log( Level.WARNING, getMessage( "unsupportedResourceType", type ), null );
1340                     }
1341                 }
1342                 catch ( final SAXException e )
1343                 {
1344                     String message = getMessage( e );
1345                     if ( message == null && e.getException() != null )
1346                     {
1347                         message = getMessage( e.getException() );
1348                     }
1349                     if ( message == null )
1350                     {
1351                         message = "";
1352                     }
1353                     else if ( message.length() > 0 )
1354                     {
1355                         message = " " + message;
1356                     }
1357 
1358                     String resource = "";
1359                     if ( resolvePublicId != null )
1360                     {
1361                         resource = resolvePublicId + ", ";
1362                     }
1363                     resource += resolveSystemId;
1364 
1365                     if ( isLoggable( Level.SEVERE ) )
1366                     {
1367                         log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1368                     }
1369                 }
1370                 catch ( final IOException e )
1371                 {
1372                     String message = getMessage( e );
1373                     if ( message == null )
1374                     {
1375                         message = "";
1376                     }
1377                     else if ( message.length() > 0 )
1378                     {
1379                         message = " " + message;
1380                     }
1381 
1382                     String resource = "";
1383                     if ( resolvePublicId != null )
1384                     {
1385                         resource = resolvePublicId + ", ";
1386                     }
1387                     resource += resolveSystemId;
1388 
1389                     if ( isLoggable( Level.SEVERE ) )
1390                     {
1391                         log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1392                     }
1393                 }
1394 
1395                 return null;
1396             }
1397 
1398         };
1399     }
1400 
1401     private javax.xml.validation.Schema createSchema( final Schemas schemas, final EntityResolver entityResolver,
1402                                                       final LSResourceResolver resourceResolver, final String model,
1403                                                       final URI publicId ) throws ModelException
1404     {
1405         if ( entityResolver == null )
1406         {
1407             throw new NullPointerException( "entityResolver" );
1408         }
1409         if ( model != null && publicId != null )
1410         {
1411             throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1412         }
1413 
1414         try
1415         {
1416             final long t0 = System.nanoTime();
1417             final SchemaFactory f = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
1418             final List<Source> sources = new ArrayList<Source>( schemas != null ? schemas.getSchema().size() : 0 );
1419 
1420             if ( schemas != null )
1421             {
1422                 for ( final Schema s : schemas.getSchema() )
1423                 {
1424                     final InputSource inputSource = entityResolver.resolveEntity( s.getPublicId(), s.getSystemId() );
1425 
1426                     if ( inputSource != null )
1427                     {
1428                         sources.add( new SAXSource( inputSource ) );
1429                     }
1430                 }
1431             }
1432 
1433             if ( sources.isEmpty() )
1434             {
1435                 if ( model != null )
1436                 {
1437                     throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1438                 }
1439                 if ( publicId != null )
1440                 {
1441                     throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1442                 }
1443             }
1444 
1445             f.setResourceResolver( resourceResolver );
1446             f.setErrorHandler( new ErrorHandler()
1447             {
1448                 // See http://java.net/jira/browse/JAXP-66
1449 
1450                 public void warning( final SAXParseException e ) throws SAXException
1451                 {
1452                     String message = getMessage( e );
1453                     if ( message == null && e.getException() != null )
1454                     {
1455                         message = getMessage( e.getException() );
1456                     }
1457 
1458                     if ( isLoggable( Level.WARNING ) )
1459                     {
1460                         log( Level.WARNING, message, e );
1461                     }
1462                 }
1463 
1464                 public void error( final SAXParseException e ) throws SAXException
1465                 {
1466                     throw e;
1467                 }
1468 
1469                 public void fatalError( final SAXParseException e ) throws SAXException
1470                 {
1471                     throw e;
1472                 }
1473 
1474             } );
1475 
1476             final javax.xml.validation.Schema schema = f.newSchema( sources.toArray( new Source[ sources.size() ] ) );
1477 
1478             if ( this.isLoggable( Level.FINE ) )
1479             {
1480                 final StringBuilder schemaInfo = new StringBuilder( sources.size() * 50 );
1481 
1482                 for ( final Source s : sources )
1483                 {
1484                     schemaInfo.append( ", " ).append( s.getSystemId() );
1485                 }
1486 
1487                 this.log( Level.FINE, getMessage( "creatingSchema", schemaInfo.substring( 2 ), System.nanoTime() - t0 ),
1488                           null );
1489 
1490             }
1491 
1492             return schema;
1493         }
1494         catch ( final IOException e )
1495         {
1496             throw new ModelException( getMessage( e ), e );
1497         }
1498         catch ( final SAXException e )
1499         {
1500             String message = getMessage( e );
1501             if ( message == null && e.getException() != null )
1502             {
1503                 message = getMessage( e.getException() );
1504             }
1505 
1506             throw new ModelException( message, e );
1507         }
1508     }
1509 
1510     private JAXBContext createContext( final Schemas schemas, final String model, final URI publicId )
1511         throws ModelException
1512     {
1513         if ( model != null && publicId != null )
1514         {
1515             throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1516         }
1517 
1518         try
1519         {
1520             StringBuilder packageNames = null;
1521             final long t0 = System.nanoTime();
1522 
1523             if ( schemas != null )
1524             {
1525                 packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1526 
1527                 for ( final Schema schema : schemas.getSchema() )
1528                 {
1529                     if ( schema.getContextId() != null )
1530                     {
1531                         packageNames.append( ':' ).append( schema.getContextId() );
1532                     }
1533                 }
1534             }
1535 
1536             if ( packageNames == null || packageNames.length() == 0 )
1537             {
1538                 if ( model != null )
1539                 {
1540                     throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1541                 }
1542                 if ( publicId != null )
1543                 {
1544                     throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1545                 }
1546             }
1547 
1548             final JAXBContext context = JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() );
1549 
1550             if ( this.isLoggable( Level.FINE ) )
1551             {
1552                 this.log( Level.FINE, getMessage( "creatingContext", packageNames.substring( 1 ),
1553                                                   System.nanoTime() - t0 ), null );
1554 
1555             }
1556 
1557             return context;
1558         }
1559         catch ( final JAXBException e )
1560         {
1561             String message = getMessage( e );
1562             if ( message == null && e.getLinkedException() != null )
1563             {
1564                 message = getMessage( e.getLinkedException() );
1565             }
1566 
1567             throw new ModelException( message, e );
1568         }
1569     }
1570 
1571     private Marshaller createMarshaller( final String model, final URI publicId )
1572         throws ModelException
1573     {
1574         if ( model != null && publicId != null )
1575         {
1576             throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1577         }
1578 
1579         Schemas schemas = null;
1580 
1581         if ( model != null )
1582         {
1583             schemas = this.getModlets().getSchemas( model );
1584         }
1585 
1586         if ( publicId != null )
1587         {
1588             schemas = this.getModlets().getSchemas( publicId );
1589         }
1590 
1591         try
1592         {
1593             StringBuilder packageNames = null;
1594             StringBuilder schemaLocation = null;
1595             final long t0 = System.nanoTime();
1596 
1597             if ( schemas != null )
1598             {
1599                 packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1600                 schemaLocation = new StringBuilder( schemas.getSchema().size() * 50 );
1601 
1602                 for ( final Schema schema : schemas.getSchema() )
1603                 {
1604                     if ( schema.getContextId() != null )
1605                     {
1606                         packageNames.append( ':' ).append( schema.getContextId() );
1607                     }
1608                     if ( schema.getPublicId() != null && schema.getSystemId() != null )
1609                     {
1610                         schemaLocation.append( ' ' ).append( schema.getPublicId() ).append( ' ' ).
1611                             append( schema.getSystemId() );
1612 
1613                     }
1614                 }
1615             }
1616 
1617             if ( packageNames == null || packageNames.length() == 0 )
1618             {
1619                 if ( model != null )
1620                 {
1621                     throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1622                 }
1623                 if ( publicId != null )
1624                 {
1625                     throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1626                 }
1627             }
1628 
1629             final Marshaller m =
1630                 JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createMarshaller();
1631 
1632             if ( schemaLocation != null && schemaLocation.length() != 0 )
1633             {
1634                 m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation.substring( 1 ) );
1635             }
1636 
1637             MarshallerListenerList listenerList = null;
1638 
1639             if ( model != null )
1640             {
1641                 final Collection<? extends Marshaller.Listener> listeners =
1642                     this.createServiceObjects( model, MARSHALLER_LISTENER_SERVICE, Marshaller.Listener.class );
1643 
1644                 if ( !listeners.isEmpty() )
1645                 {
1646                     listenerList = new MarshallerListenerList();
1647                     listenerList.getListeners().addAll( listeners );
1648                     m.setListener( listenerList );
1649                 }
1650             }
1651 
1652             if ( this.isLoggable( Level.FINE ) )
1653             {
1654                 if ( listenerList == null )
1655                 {
1656                     this.log( Level.FINE, getMessage( "creatingMarshaller", packageNames.substring( 1 ),
1657                                                       schemaLocation.substring( 1 ),
1658                                                       System.nanoTime() - t0 ), null );
1659 
1660                 }
1661                 else
1662                 {
1663                     final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
1664 
1665                     for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
1666                     {
1667                         b.append( ',' ).append( listenerList.getListeners().get( i ) );
1668                     }
1669 
1670                     this.log( Level.FINE, getMessage( "creatingMarshallerWithListeners", packageNames.substring( 1 ),
1671                                                       schemaLocation.substring( 1 ), b.substring( 1 ),
1672                                                       System.nanoTime() - t0 ), null );
1673 
1674                 }
1675             }
1676 
1677             return m;
1678         }
1679         catch ( final JAXBException e )
1680         {
1681             String message = getMessage( e );
1682             if ( message == null && e.getLinkedException() != null )
1683             {
1684                 message = getMessage( e.getLinkedException() );
1685             }
1686 
1687             throw new ModelException( message, e );
1688         }
1689     }
1690 
1691     private Unmarshaller createUnmarshaller( final String model, final URI publicId )
1692         throws ModelException
1693     {
1694         if ( model != null && publicId != null )
1695         {
1696             throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1697         }
1698 
1699         Schemas schemas = null;
1700 
1701         if ( model != null )
1702         {
1703             schemas = this.getModlets().getSchemas( model );
1704         }
1705 
1706         if ( publicId != null )
1707         {
1708             schemas = this.getModlets().getSchemas( publicId );
1709         }
1710 
1711         try
1712         {
1713             StringBuilder packageNames = null;
1714             final long t0 = System.nanoTime();
1715 
1716             if ( schemas != null )
1717             {
1718                 packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1719 
1720                 for ( final Schema schema : schemas.getSchema() )
1721                 {
1722                     if ( schema.getContextId() != null )
1723                     {
1724                         packageNames.append( ':' ).append( schema.getContextId() );
1725                     }
1726                 }
1727             }
1728 
1729             if ( packageNames == null || packageNames.length() == 0 )
1730             {
1731                 if ( model != null )
1732                 {
1733                     throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1734                 }
1735                 if ( publicId != null )
1736                 {
1737                     throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1738                 }
1739             }
1740 
1741             final Unmarshaller u =
1742                 JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createUnmarshaller();
1743 
1744             UnmarshallerListenerList listenerList = null;
1745 
1746             if ( model != null )
1747             {
1748                 final Collection<? extends Unmarshaller.Listener> listeners =
1749                     this.createServiceObjects( model, UNMARSHALLER_LISTENER_SERVICE, Unmarshaller.Listener.class );
1750 
1751                 if ( !listeners.isEmpty() )
1752                 {
1753                     listenerList = new UnmarshallerListenerList();
1754                     listenerList.getListeners().addAll( listeners );
1755                     u.setListener( listenerList );
1756                 }
1757             }
1758 
1759             if ( this.isLoggable( Level.FINE ) )
1760             {
1761                 if ( listenerList == null )
1762                 {
1763                     this.log( Level.FINE, getMessage( "creatingUnmarshaller", packageNames.substring( 1 ),
1764                                                       System.nanoTime() - t0 ), null );
1765 
1766                 }
1767                 else
1768                 {
1769                     final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
1770 
1771                     for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
1772                     {
1773                         b.append( ',' ).append( listenerList.getListeners().get( i ) );
1774                     }
1775 
1776                     this.log( Level.FINE, getMessage( "creatingUnmarshallerWithListeners",
1777                                                       packageNames.substring( 1 ), b.substring( 1 ),
1778                                                       System.nanoTime() - t0 ), null );
1779 
1780                 }
1781             }
1782 
1783             return u;
1784         }
1785         catch ( final JAXBException e )
1786         {
1787             String message = getMessage( e );
1788             if ( message == null && e.getLinkedException() != null )
1789             {
1790                 message = getMessage( e.getLinkedException() );
1791             }
1792 
1793             throw new ModelException( message, e );
1794         }
1795     }
1796 
1797     /**
1798      * {@inheritDoc}
1799      * <p>
1800      * This method loads {@code ServiceFactory} classes setup via the platform provider configuration file and
1801      * {@code <provider-location>/org.jomc.modlet.ServiceFactory} resources to create new service objects.
1802      * </p>
1803      *
1804      * @since 1.9
1805      */
1806     @Override
1807     public <T> Collection<? extends T> createServiceObjects( final String model, final String service,
1808                                                              final Class<T> type )
1809         throws ModelException
1810     {
1811         if ( model == null )
1812         {
1813             throw new NullPointerException( "model" );
1814         }
1815         if ( service == null )
1816         {
1817             throw new NullPointerException( "service" );
1818         }
1819         if ( type == null )
1820         {
1821             throw new NullPointerException( "type" );
1822         }
1823 
1824         final Services modelServices = this.getModlets().getServices( model );
1825         final Collection<T> serviceObjects =
1826             new ArrayList<T>( modelServices != null ? modelServices.getService().size() : 0 );
1827 
1828         if ( modelServices != null )
1829         {
1830             final Collection<ServiceFactory> factories = this.loadModletServices( ServiceFactory.class );
1831 
1832             for ( final Service s : modelServices.getServices( service ) )
1833             {
1834                 serviceObjects.add( this.createServiceObject( s, type, factories ) );
1835             }
1836         }
1837 
1838         return Collections.unmodifiableCollection( serviceObjects );
1839     }
1840 
1841     /**
1842      * This method creates a new service object for a given service using a given collection of service factories.
1843      *
1844      * @param <T> The type of the service.
1845      * @param service The service to create a new object of.
1846      * @param type The class of the type of the service.
1847      * @param factories The service factories to use for creating the new service object.
1848      *
1849      * @return An new service object for {@code service}.
1850      *
1851      * @throws NullPointerException if {@code service}, {@code type} or {@code factories} is {@code null}.
1852      * @throws ModelException if creating the service object fails.
1853      * @since 1.9
1854      */
1855     private <T> T createServiceObject( final Service service, final Class<T> type,
1856                                        final Collection<ServiceFactory> factories ) throws ModelException
1857     {
1858         if ( service == null )
1859         {
1860             throw new NullPointerException( "service" );
1861         }
1862         if ( type == null )
1863         {
1864             throw new NullPointerException( "type" );
1865         }
1866         if ( factories == null )
1867         {
1868             throw new NullPointerException( "factories" );
1869         }
1870 
1871         T serviceObject = null;
1872 
1873         for ( final ServiceFactory factory : factories )
1874         {
1875             final T current = factory.createServiceObject( this, service, type );
1876 
1877             if ( current != null )
1878             {
1879                 if ( this.isLoggable( Level.FINER ) )
1880                 {
1881                     this.log( Level.FINER, getMessage( "creatingService", service.getOrdinal(), service.getIdentifier(),
1882                                                        service.getClazz(), factory.toString() ), null );
1883 
1884                 }
1885 
1886                 serviceObject = current;
1887                 break;
1888             }
1889         }
1890 
1891         if ( serviceObject == null )
1892         {
1893             throw new ModelException( getMessage( "serviceNotCreated", service.getOrdinal(), service.getIdentifier(),
1894                                                   service.getClazz() ), null );
1895 
1896         }
1897 
1898         return serviceObject;
1899     }
1900 
1901     private <T> Collection<T> loadModletServices( final Class<T> serviceClass ) throws ModelException
1902     {
1903         InputStream in = null;
1904         BufferedReader reader = null;
1905 
1906         try
1907         {
1908             final String serviceNamePrefix = serviceClass.getName() + ".";
1909             final Map<String, T> sortedPlatformServices = new TreeMap<String, T>( new Comparator<String>()
1910             {
1911 
1912                 public int compare( final String key1, final String key2 )
1913                 {
1914                     return key1.compareTo( key2 );
1915                 }
1916 
1917             } );
1918 
1919             final File platformServices = new File( this.getPlatformProviderLocation() );
1920 
1921             if ( platformServices.exists() )
1922             {
1923                 if ( this.isLoggable( Level.FINEST ) )
1924                 {
1925                     this.log( Level.FINEST, getMessage( "processing", platformServices.getAbsolutePath() ), null );
1926                 }
1927 
1928                 final java.util.Properties p = new java.util.Properties();
1929 
1930                 in = new FileInputStream( platformServices );
1931 
1932                 p.load( in );
1933 
1934                 in.close();
1935                 in = null;
1936 
1937                 for ( final Map.Entry<Object, Object> e : p.entrySet() )
1938                 {
1939                     if ( e.getKey().toString().startsWith( serviceNamePrefix ) )
1940                     {
1941                         final String configuration = e.getValue().toString();
1942 
1943                         if ( this.isLoggable( Level.FINEST ) )
1944                         {
1945                             this.log( Level.FINEST, getMessage( "serviceInfo", platformServices.getAbsolutePath(),
1946                                                                 serviceClass.getName(), configuration ), null );
1947 
1948                         }
1949 
1950                         sortedPlatformServices.put( e.getKey().toString(),
1951                                                     this.createModletServiceObject( serviceClass, configuration ) );
1952 
1953                     }
1954                 }
1955             }
1956 
1957             final Enumeration<URL> classpathServices =
1958                 this.findResources( this.getProviderLocation() + '/' + serviceClass.getName() );
1959 
1960             int count = 0;
1961             final long t0 = System.nanoTime();
1962             final List<T> sortedClasspathServices = new LinkedList<T>();
1963 
1964             while ( classpathServices.hasMoreElements() )
1965             {
1966                 count++;
1967                 final URL url = classpathServices.nextElement();
1968 
1969                 if ( this.isLoggable( Level.FINEST ) )
1970                 {
1971                     this.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
1972                 }
1973 
1974                 reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) );
1975 
1976                 for ( String line = reader.readLine(); line != null; line = reader.readLine() )
1977                 {
1978                     if ( line.contains( "#" ) )
1979                     {
1980                         continue;
1981                     }
1982 
1983                     if ( this.isLoggable( Level.FINEST ) )
1984                     {
1985                         this.log( Level.FINEST, getMessage( "serviceInfo", url.toExternalForm(),
1986                                                             serviceClass.getName(), line ), null );
1987 
1988                     }
1989 
1990                     final T serviceObject = this.createModletServiceObject( serviceClass, line );
1991                     sortedClasspathServices.add( serviceObject );
1992                 }
1993 
1994                 Collections.sort( sortedClasspathServices,
1995                                   new Comparator<Object>()
1996                               {
1997 
1998                                   public int compare( final Object o1, final Object o2 )
1999                                   {
2000                                       return ordinalOf( o1 ) - ordinalOf( o2 );
2001                                   }
2002 
2003                               } );
2004 
2005                 reader.close();
2006                 reader = null;
2007             }
2008 
2009             if ( this.isLoggable( Level.FINE ) )
2010             {
2011                 this.log( Level.FINE, getMessage( "contextReport", count,
2012                                                   this.getProviderLocation() + '/' + serviceClass.getName(),
2013                                                   System.nanoTime() - t0 ), null );
2014 
2015             }
2016 
2017             final List<T> services =
2018                 new ArrayList<T>( sortedPlatformServices.size() + sortedClasspathServices.size() );
2019 
2020             services.addAll( sortedPlatformServices.values() );
2021             services.addAll( sortedClasspathServices );
2022 
2023             return services;
2024         }
2025         catch ( final IOException e )
2026         {
2027             throw new ModelException( getMessage( e ), e );
2028         }
2029         finally
2030         {
2031             try
2032             {
2033                 if ( in != null )
2034                 {
2035                     in.close();
2036                 }
2037             }
2038             catch ( final IOException e )
2039             {
2040                 this.log( Level.SEVERE, getMessage( e ), e );
2041             }
2042             finally
2043             {
2044                 try
2045                 {
2046                     if ( reader != null )
2047                     {
2048                         reader.close();
2049                     }
2050                 }
2051                 catch ( final IOException e )
2052                 {
2053                     this.log( Level.SEVERE, getMessage( e ), e );
2054                 }
2055             }
2056         }
2057     }
2058 
2059     private <T> T createModletServiceObject( final Class<T> serviceClass, final String configuration )
2060         throws ModelException
2061     {
2062         String className = configuration;
2063         final int i0 = configuration.indexOf( '[' );
2064         final int i1 = configuration.lastIndexOf( ']' );
2065         final Service service = new Service();
2066         service.setIdentifier( serviceClass.getName() );
2067 
2068         if ( i0 != -1 && i1 != -1 )
2069         {
2070             className = configuration.substring( 0, i0 );
2071             final StringTokenizer propertyTokens =
2072                 new StringTokenizer( configuration.substring( i0 + 1, i1 ), "," );
2073 
2074             while ( propertyTokens.hasMoreTokens() )
2075             {
2076                 final String property = propertyTokens.nextToken();
2077                 final int d0 = property.indexOf( '=' );
2078 
2079                 String propertyName = property;
2080                 String propertyValue = null;
2081 
2082                 if ( d0 != -1 )
2083                 {
2084                     propertyName = property.substring( 0, d0 );
2085                     propertyValue = property.substring( d0 + 1, property.length() );
2086                 }
2087 
2088                 final Property p = new Property();
2089                 service.getProperty().add( p );
2090 
2091                 p.setName( propertyName );
2092                 p.setValue( propertyValue );
2093             }
2094         }
2095 
2096         service.setClazz( className );
2097 
2098         // Need a way to exchange the service factory creating modlet service objects?
2099         return new DefaultServiceFactory().createServiceObject( this, service, serviceClass );
2100     }
2101 
2102     /**
2103      * Searches the context for {@code META-INF/MANIFEST.MF} resources and returns a set of URIs of entries whose names
2104      * end with a known schema extension.
2105      *
2106      * @return Set of URIs of any matching entries.
2107      *
2108      * @throws IOException if reading fails.
2109      * @throws URISyntaxException if parsing fails.
2110      * @throws ModelException if searching the context fails.
2111      */
2112     private Set<URI> getSchemaResources() throws IOException, URISyntaxException, ModelException
2113     {
2114         final Set<URI> resources = new HashSet<URI>();
2115         final long t0 = System.nanoTime();
2116         int count = 0;
2117 
2118         for ( final Enumeration<URL> e = this.findResources( "META-INF/MANIFEST.MF" ); e.hasMoreElements(); )
2119         {
2120             InputStream manifestStream = null;
2121 
2122             try
2123             {
2124                 count++;
2125                 final URL manifestUrl = e.nextElement();
2126                 final String externalForm = manifestUrl.toExternalForm();
2127                 final String baseUrl = externalForm.substring( 0, externalForm.indexOf( "META-INF" ) );
2128                 manifestStream = manifestUrl.openStream();
2129                 final Manifest mf = new Manifest( manifestStream );
2130 
2131                 if ( this.isLoggable( Level.FINEST ) )
2132                 {
2133                     this.log( Level.FINEST, getMessage( "processing", externalForm ), null );
2134                 }
2135 
2136                 for ( final Map.Entry<String, Attributes> entry : mf.getEntries().entrySet() )
2137                 {
2138                     for ( int i = SCHEMA_EXTENSIONS.length - 1; i >= 0; i-- )
2139                     {
2140                         if ( entry.getKey().toLowerCase().endsWith( '.' + SCHEMA_EXTENSIONS[i].toLowerCase() ) )
2141                         {
2142                             final URL schemaUrl = new URL( baseUrl + entry.getKey() );
2143                             resources.add( schemaUrl.toURI() );
2144 
2145                             if ( this.isLoggable( Level.FINEST ) )
2146                             {
2147                                 this.log( Level.FINEST, getMessage( "foundSchemaCandidate",
2148                                                                     schemaUrl.toExternalForm() ), null );
2149 
2150                             }
2151                         }
2152                     }
2153                 }
2154 
2155                 manifestStream.close();
2156                 manifestStream = null;
2157             }
2158             finally
2159             {
2160                 try
2161                 {
2162                     if ( manifestStream != null )
2163                     {
2164                         manifestStream.close();
2165                     }
2166                 }
2167                 catch ( final IOException ex )
2168                 {
2169                     this.log( Level.SEVERE, getMessage( ex ), ex );
2170                 }
2171             }
2172         }
2173 
2174         if ( this.isLoggable( Level.FINE ) )
2175         {
2176             this.log( Level.FINE, getMessage( "contextReport", count, "META-INF/MANIFEST.MF", System.nanoTime() - t0 ),
2177                       null );
2178 
2179         }
2180 
2181         return resources;
2182     }
2183 
2184     private static int ordinalOf( final Object serviceObject )
2185     {
2186         int ordinal = 0;
2187 
2188         if ( serviceObject instanceof ModletProvider )
2189         {
2190             ordinal = ( (ModletProvider) serviceObject ).getOrdinal();
2191         }
2192         if ( serviceObject instanceof ModletProcessor )
2193         {
2194             ordinal = ( (ModletProcessor) serviceObject ).getOrdinal();
2195         }
2196         if ( serviceObject instanceof ModletValidator )
2197         {
2198             ordinal = ( (ModletValidator) serviceObject ).getOrdinal();
2199         }
2200         if ( serviceObject instanceof ServiceFactory )
2201         {
2202             ordinal = ( (ServiceFactory) serviceObject ).getOrdinal();
2203         }
2204 
2205         return ordinal;
2206     }
2207 
2208     private static String getMessage( final String key, final Object... arguments )
2209     {
2210         return MessageFormat.format( ResourceBundle.getBundle(
2211             DefaultModelContext.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2212 
2213     }
2214 
2215     private static String getMessage( final Throwable t )
2216     {
2217         return t != null
2218                    ? t.getMessage() != null && t.getMessage().trim().length() > 0
2219                          ? t.getMessage()
2220                          : getMessage( t.getCause() )
2221                    : null;
2222 
2223     }
2224 
2225 }
2226 
2227 /**
2228  * {@code ErrorHandler} collecting {@code ModelValidationReport} details.
2229  *
2230  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2231  * @version $JOMC: DefaultModelContext.java 5391 2016-09-30 21:25:10Z schulte $
2232  */
2233 class ModelErrorHandler extends DefaultHandler
2234 {
2235 
2236     /**
2237      * The context of the instance.
2238      */
2239     private final ModelContext context;
2240 
2241     /**
2242      * The report of the instance.
2243      */
2244     private final ModelValidationReport report;
2245 
2246     /**
2247      * Creates a new {@code ModelErrorHandler} instance taking a context.
2248      *
2249      * @param context The context of the instance.
2250      */
2251     ModelErrorHandler( final ModelContext context )
2252     {
2253         this( context, new ModelValidationReport() );
2254     }
2255 
2256     /**
2257      * Creates a new {@code ModelErrorHandler} instance taking a report to use for collecting validation events.
2258      *
2259      * @param context The context of the instance.
2260      * @param report A report to use for collecting validation events.
2261      */
2262     ModelErrorHandler( final ModelContext context, final ModelValidationReport report )
2263     {
2264         super();
2265         this.context = context;
2266         this.report = report;
2267     }
2268 
2269     /**
2270      * Gets the report of the instance.
2271      *
2272      * @return The report of the instance.
2273      */
2274     public ModelValidationReport getReport()
2275     {
2276         return this.report;
2277     }
2278 
2279     @Override
2280     public void warning( final SAXParseException exception ) throws SAXException
2281     {
2282         String message = getMessage( exception );
2283         if ( message == null && exception.getException() != null )
2284         {
2285             message = getMessage( exception.getException() );
2286         }
2287 
2288         if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2289         {
2290             this.context.log( Level.FINE, message, exception );
2291         }
2292 
2293         this.getReport().getDetails().add( new ModelValidationReport.Detail(
2294             "W3C XML 1.0 Recommendation - Warning condition", Level.WARNING, message, null ) );
2295 
2296     }
2297 
2298     @Override
2299     public void error( final SAXParseException exception ) throws SAXException
2300     {
2301         String message = getMessage( exception );
2302         if ( message == null && exception.getException() != null )
2303         {
2304             message = getMessage( exception.getException() );
2305         }
2306 
2307         if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2308         {
2309             this.context.log( Level.FINE, message, exception );
2310         }
2311 
2312         this.getReport().getDetails().add( new ModelValidationReport.Detail(
2313             "W3C XML 1.0 Recommendation - Section 1.2 - Error", Level.SEVERE, message, null ) );
2314 
2315     }
2316 
2317     @Override
2318     public void fatalError( final SAXParseException exception ) throws SAXException
2319     {
2320         String message = getMessage( exception );
2321         if ( message == null && exception.getException() != null )
2322         {
2323             message = getMessage( exception.getException() );
2324         }
2325 
2326         if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2327         {
2328             this.context.log( Level.FINE, message, exception );
2329         }
2330 
2331         this.getReport().getDetails().add( new ModelValidationReport.Detail(
2332             "W3C XML 1.0 Recommendation - Section 1.2 - Fatal Error", Level.SEVERE, message, null ) );
2333 
2334     }
2335 
2336     private static String getMessage( final Throwable t )
2337     {
2338         return t != null
2339                    ? t.getMessage() != null && t.getMessage().trim().length() > 0
2340                          ? t.getMessage()
2341                          : getMessage( t.getCause() )
2342                    : null;
2343 
2344     }
2345 
2346 }
2347 
2348 /**
2349  * List of {@code Marshaller.Listener}s.
2350  *
2351  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2352  * @version $JOMC: DefaultModelContext.java 5391 2016-09-30 21:25:10Z schulte $
2353  * @since 1.2
2354  */
2355 class MarshallerListenerList extends Marshaller.Listener
2356 {
2357 
2358     /**
2359      * The {@code Marshaller.Listener}s of the instance.
2360      */
2361     private final List<Marshaller.Listener> listeners = new CopyOnWriteArrayList<Marshaller.Listener>();
2362 
2363     /**
2364      * Creates a new {@code MarshallerListenerList} instance.
2365      */
2366     MarshallerListenerList()
2367     {
2368         super();
2369     }
2370 
2371     /**
2372      * Gets the listeners of the instance.
2373      * <p>
2374      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2375      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2376      * listeners property.
2377      * </p>
2378      *
2379      * @return The list of listeners of the instance.
2380      */
2381     List<Marshaller.Listener> getListeners()
2382     {
2383         return this.listeners;
2384     }
2385 
2386     @Override
2387     public void beforeMarshal( final Object source )
2388     {
2389         for ( final Marshaller.Listener listener : this.getListeners() )
2390         {
2391             listener.beforeMarshal( source );
2392         }
2393     }
2394 
2395     @Override
2396     public void afterMarshal( final Object source )
2397     {
2398         for ( final Marshaller.Listener listener : this.getListeners() )
2399         {
2400             listener.afterMarshal( source );
2401         }
2402     }
2403 
2404 }
2405 
2406 /**
2407  * List of {@code Unmarshaller.Listener}s.
2408  *
2409  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2410  * @version $JOMC: DefaultModelContext.java 5391 2016-09-30 21:25:10Z schulte $
2411  * @since 1.2
2412  */
2413 class UnmarshallerListenerList extends Unmarshaller.Listener
2414 {
2415 
2416     /**
2417      * The {@code Unmarshaller.Listener}s of the instance.
2418      */
2419     private final List<Unmarshaller.Listener> listeners = new CopyOnWriteArrayList<Unmarshaller.Listener>();
2420 
2421     /**
2422      * Creates a new {@code UnmarshallerListenerList} instance.
2423      */
2424     UnmarshallerListenerList()
2425     {
2426         super();
2427     }
2428 
2429     /**
2430      * Gets the listeners of the instance.
2431      * <p>
2432      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2433      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2434      * listeners property.
2435      * </p>
2436      *
2437      * @return The list of listeners of the instance.
2438      */
2439     List<Unmarshaller.Listener> getListeners()
2440     {
2441         return this.listeners;
2442     }
2443 
2444     @Override
2445     public void beforeUnmarshal( final Object target, final Object parent )
2446     {
2447         for ( final Unmarshaller.Listener listener : this.getListeners() )
2448         {
2449             listener.beforeUnmarshal( target, parent );
2450         }
2451     }
2452 
2453     @Override
2454     public void afterUnmarshal( final Object target, final Object parent )
2455     {
2456         for ( final Unmarshaller.Listener listener : this.getListeners() )
2457         {
2458             listener.afterUnmarshal( target, parent );
2459         }
2460     }
2461 
2462 }