View Javadoc

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