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