001/*
002 *   Copyright (C) Christian Schulte, 2005-206
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
029 *
030 */
031package org.jomc.modlet;
032
033import java.io.BufferedReader;
034import java.io.File;
035import java.io.FileInputStream;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.InputStreamReader;
039import java.io.Reader;
040import java.lang.ref.Reference;
041import java.lang.ref.SoftReference;
042import java.lang.reflect.InvocationTargetException;
043import java.lang.reflect.Method;
044import java.lang.reflect.Modifier;
045import java.net.URI;
046import java.net.URISyntaxException;
047import java.net.URL;
048import java.text.MessageFormat;
049import java.util.ArrayList;
050import java.util.Collection;
051import java.util.Comparator;
052import java.util.Enumeration;
053import java.util.HashMap;
054import java.util.HashSet;
055import java.util.List;
056import java.util.Map;
057import java.util.ResourceBundle;
058import java.util.Set;
059import java.util.StringTokenizer;
060import java.util.TreeMap;
061import java.util.jar.Attributes;
062import java.util.jar.Manifest;
063import java.util.logging.Level;
064import javax.xml.XMLConstants;
065import javax.xml.bind.JAXBContext;
066import javax.xml.bind.JAXBException;
067import javax.xml.bind.Marshaller;
068import javax.xml.bind.Unmarshaller;
069import javax.xml.transform.Source;
070import javax.xml.transform.sax.SAXSource;
071import javax.xml.validation.SchemaFactory;
072import javax.xml.validation.Validator;
073import org.w3c.dom.ls.LSInput;
074import org.w3c.dom.ls.LSResourceResolver;
075import org.xml.sax.EntityResolver;
076import org.xml.sax.ErrorHandler;
077import org.xml.sax.InputSource;
078import org.xml.sax.SAXException;
079import org.xml.sax.SAXParseException;
080import org.xml.sax.helpers.DefaultHandler;
081
082/**
083 * Default {@code ModelContext} implementation.
084 *
085 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
086 * @version $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
087 * @see ModelContextFactory
088 */
089public class DefaultModelContext extends ModelContext
090{
091
092    /**
093     * Constant for the name of the model context attribute backing property {@code providerLocation}.
094     * @see #getProviderLocation()
095     * @see ModelContext#getAttribute(java.lang.String)
096     * @since 1.2
097     */
098    public static final String PROVIDER_LOCATION_ATTRIBUTE_NAME =
099        "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            Class<?> unboxedPropertyType = propertyType;
1119
1120            if ( Boolean.TYPE.equals( propertyType ) )
1121            {
1122                boxedPropertyType = Boolean.class;
1123            }
1124            else if ( Character.TYPE.equals( propertyType ) )
1125            {
1126                boxedPropertyType = Character.class;
1127            }
1128            else if ( Byte.TYPE.equals( propertyType ) )
1129            {
1130                boxedPropertyType = Byte.class;
1131            }
1132            else if ( Short.TYPE.equals( propertyType ) )
1133            {
1134                boxedPropertyType = Short.class;
1135            }
1136            else if ( Integer.TYPE.equals( propertyType ) )
1137            {
1138                boxedPropertyType = Integer.class;
1139            }
1140            else if ( Long.TYPE.equals( propertyType ) )
1141            {
1142                boxedPropertyType = Long.class;
1143            }
1144            else if ( Float.TYPE.equals( propertyType ) )
1145            {
1146                boxedPropertyType = Float.class;
1147            }
1148            else if ( Double.TYPE.equals( propertyType ) )
1149            {
1150                boxedPropertyType = Double.class;
1151            }
1152
1153            if ( Boolean.class.equals( propertyType ) )
1154            {
1155                unboxedPropertyType = Boolean.TYPE;
1156            }
1157            else if ( Character.class.equals( propertyType ) )
1158            {
1159                unboxedPropertyType = Character.TYPE;
1160            }
1161            else if ( Byte.class.equals( propertyType ) )
1162            {
1163                unboxedPropertyType = Byte.TYPE;
1164            }
1165            else if ( Short.class.equals( propertyType ) )
1166            {
1167                unboxedPropertyType = Short.TYPE;
1168            }
1169            else if ( Integer.class.equals( propertyType ) )
1170            {
1171                unboxedPropertyType = Integer.TYPE;
1172            }
1173            else if ( Long.class.equals( propertyType ) )
1174            {
1175                unboxedPropertyType = Long.TYPE;
1176            }
1177            else if ( Float.class.equals( propertyType ) )
1178            {
1179                unboxedPropertyType = Float.TYPE;
1180            }
1181            else if ( Double.class.equals( propertyType ) )
1182            {
1183                unboxedPropertyType = Double.TYPE;
1184            }
1185
1186            Method setterMethod = null;
1187
1188            try
1189            {
1190                setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, boxedPropertyType );
1191            }
1192            catch ( final NoSuchMethodException e )
1193            {
1194                if ( this.isLoggable( Level.FINEST ) )
1195                {
1196                    this.log( Level.FINEST, null, e );
1197                }
1198
1199                setterMethod = null;
1200            }
1201
1202            if ( setterMethod == null && !boxedPropertyType.equals( unboxedPropertyType ) )
1203            {
1204                try
1205                {
1206                    setterMethod = object.getClass().getMethod( "set" + methodNameSuffix, unboxedPropertyType );
1207                }
1208                catch ( final NoSuchMethodException e )
1209                {
1210                    if ( this.isLoggable( Level.FINEST ) )
1211                    {
1212                        this.log( Level.FINEST, null, e );
1213                    }
1214
1215                    setterMethod = null;
1216                }
1217            }
1218
1219            if ( setterMethod == null )
1220            {
1221                throw new ModelException( getMessage( "setterMethodNotFound", object.getClass().getName(),
1222                                                      propertyName ) );
1223
1224            }
1225
1226            if ( boxedPropertyType.equals( Character.class ) )
1227            {
1228                if ( propertyValue == null || propertyValue.length() != 1 )
1229                {
1230                    throw new ModelException( getMessage( "unsupportedCharacterValue", object.getClass().getName(),
1231                                                          propertyName ) );
1232
1233                }
1234
1235                setterMethod.invoke( object, Character.valueOf( propertyValue.charAt( 0 ) ) );
1236                return;
1237            }
1238
1239            if ( propertyValue != null )
1240            {
1241                if ( boxedPropertyType.equals( String.class ) )
1242                {
1243                    setterMethod.invoke( object, propertyValue );
1244                    return;
1245                }
1246
1247                try
1248                {
1249                    setterMethod.invoke(
1250                        object, boxedPropertyType.getConstructor( String.class ).newInstance( propertyValue ) );
1251
1252                    return;
1253                }
1254                catch ( final NoSuchMethodException e )
1255                {
1256                    if ( this.isLoggable( Level.FINEST ) )
1257                    {
1258                        this.log( Level.FINEST, null, e );
1259                    }
1260                }
1261
1262                try
1263                {
1264                    final Method valueOf = boxedPropertyType.getMethod( "valueOf", String.class );
1265
1266                    if ( Modifier.isStatic( valueOf.getModifiers() )
1267                         && ( valueOf.getReturnType().equals( boxedPropertyType )
1268                              || valueOf.getReturnType().equals( unboxedPropertyType ) ) )
1269                    {
1270                        setterMethod.invoke( object, valueOf.invoke( null, propertyValue ) );
1271                        return;
1272                    }
1273                }
1274                catch ( final NoSuchMethodException e )
1275                {
1276                    if ( this.isLoggable( Level.FINEST ) )
1277                    {
1278                        this.log( Level.FINEST, null, e );
1279                    }
1280                }
1281
1282                throw new ModelException( getMessage( "unsupportedPropertyType", object.getClass().getName(),
1283                                                      propertyName, propertyType.getName() ) );
1284
1285            }
1286            else
1287            {
1288                setterMethod.invoke( object, (Object) null );
1289            }
1290        }
1291        catch ( final IllegalAccessException e )
1292        {
1293            throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1294                                                  object.getClass().getName() ), e );
1295
1296        }
1297        catch ( final InvocationTargetException e )
1298        {
1299            throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1300                                                  object.getClass().getName() ), e );
1301
1302        }
1303        catch ( final InstantiationException e )
1304        {
1305            throw new ModelException( getMessage( "failedSettingProperty", propertyName, object.toString(),
1306                                                  object.getClass().getName() ), e );
1307
1308        }
1309    }
1310
1311    /**
1312     * Searches the context for {@code META-INF/MANIFEST.MF} resources and returns a set of URIs of entries whose names
1313     * end with a known schema extension.
1314     *
1315     * @return Set of URIs of any matching entries.
1316     *
1317     * @throws IOException if reading fails.
1318     * @throws URISyntaxException if parsing fails.
1319     * @throws ModelException if searching the context fails.
1320     */
1321    private Set<URI> getSchemaResources() throws IOException, URISyntaxException, ModelException
1322    {
1323        Set<URI> resources = this.cachedSchemaResources.get();
1324
1325        if ( resources == null )
1326        {
1327            resources = new HashSet<URI>();
1328            final long t0 = System.currentTimeMillis();
1329            int count = 0;
1330
1331            for ( final Enumeration<URL> e = this.findResources( "META-INF/MANIFEST.MF" );
1332                  e.hasMoreElements(); )
1333            {
1334                InputStream manifestStream = null;
1335                boolean suppressExceptionOnClose = true;
1336
1337                try
1338                {
1339                    count++;
1340                    final URL manifestUrl = e.nextElement();
1341                    final String externalForm = manifestUrl.toExternalForm();
1342                    final String baseUrl = externalForm.substring( 0, externalForm.indexOf( "META-INF" ) );
1343                    manifestStream = manifestUrl.openStream();
1344                    final Manifest mf = new Manifest( manifestStream );
1345
1346                    if ( this.isLoggable( Level.FINEST ) )
1347                    {
1348                        this.log( Level.FINEST, getMessage( "processing", externalForm ), null );
1349                    }
1350
1351                    for ( Map.Entry<String, Attributes> entry : mf.getEntries().entrySet() )
1352                    {
1353                        for ( int i = SCHEMA_EXTENSIONS.length - 1; i >= 0; i-- )
1354                        {
1355                            if ( entry.getKey().toLowerCase().endsWith( '.' + SCHEMA_EXTENSIONS[i].toLowerCase() ) )
1356                            {
1357                                final URL schemaUrl = new URL( baseUrl + entry.getKey() );
1358                                resources.add( schemaUrl.toURI() );
1359
1360                                if ( this.isLoggable( Level.FINEST ) )
1361                                {
1362                                    this.log( Level.FINEST, getMessage( "foundSchemaCandidate",
1363                                                                        schemaUrl.toExternalForm() ), null );
1364
1365                                }
1366                            }
1367                        }
1368                    }
1369
1370                    suppressExceptionOnClose = false;
1371                }
1372                finally
1373                {
1374                    try
1375                    {
1376                        if ( manifestStream != null )
1377                        {
1378                            manifestStream.close();
1379                        }
1380                    }
1381                    catch ( final IOException ex )
1382                    {
1383                        if ( suppressExceptionOnClose )
1384                        {
1385                            this.log( Level.SEVERE, getMessage( ex ), ex );
1386                        }
1387                        else
1388                        {
1389                            throw ex;
1390                        }
1391                    }
1392                }
1393            }
1394
1395            if ( this.isLoggable( Level.FINE ) )
1396            {
1397                this.log( Level.FINE, getMessage( "contextReport", count, "META-INF/MANIFEST.MF",
1398                                                  Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
1399
1400            }
1401
1402            this.cachedSchemaResources = new SoftReference<Set<URI>>( resources );
1403        }
1404
1405        return resources;
1406    }
1407
1408    private EntityResolver createEntityResolver( final Schemas schemas )
1409    {
1410        return new DefaultHandler()
1411        {
1412
1413            @Override
1414            public InputSource resolveEntity( final String publicId, final String systemId )
1415                throws SAXException, IOException
1416            {
1417                if ( systemId == null )
1418                {
1419                    throw new NullPointerException( "systemId" );
1420                }
1421
1422                InputSource schemaSource = null;
1423
1424                try
1425                {
1426                    Schema s = null;
1427
1428                    if ( schemas != null )
1429                    {
1430                        s = schemas.getSchemaBySystemId( systemId );
1431
1432                        if ( s == null && publicId != null )
1433                        {
1434                            try
1435                            {
1436                                final List<Schema> schemasByPublicId =
1437                                    schemas.getSchemasByPublicId( new URI( publicId ) );
1438
1439                                if ( schemasByPublicId.size() == 1 )
1440                                {
1441                                    s = schemasByPublicId.get( 0 );
1442                                }
1443                            }
1444                            catch ( final URISyntaxException e )
1445                            {
1446                                if ( isLoggable( Level.WARNING ) )
1447                                {
1448                                    log( Level.WARNING, getMessage( "unsupportedIdUri", publicId, getMessage( e ) ),
1449                                         null );
1450
1451                                }
1452
1453                                s = null;
1454                            }
1455                        }
1456                    }
1457
1458                    if ( s != null )
1459                    {
1460                        schemaSource = new InputSource();
1461                        schemaSource.setPublicId( s.getPublicId() != null ? s.getPublicId() : publicId );
1462                        schemaSource.setSystemId( s.getSystemId() );
1463
1464                        if ( s.getClasspathId() != null )
1465                        {
1466                            final URL resource = findResource( s.getClasspathId() );
1467
1468                            if ( resource != null )
1469                            {
1470                                schemaSource.setSystemId( resource.toExternalForm() );
1471                            }
1472                            else if ( isLoggable( Level.WARNING ) )
1473                            {
1474                                log( Level.WARNING, getMessage( "resourceNotFound", s.getClasspathId() ), null );
1475                            }
1476                        }
1477
1478                        if ( isLoggable( Level.FINEST ) )
1479                        {
1480                            log( Level.FINEST, getMessage( "resolutionInfo", publicId + ", " + systemId,
1481                                                           schemaSource.getPublicId() + ", "
1482                                                           + schemaSource.getSystemId() ), null );
1483
1484                        }
1485                    }
1486
1487                    if ( schemaSource == null )
1488                    {
1489                        final URI systemUri = new URI( systemId );
1490                        String schemaName = systemUri.getPath();
1491
1492                        if ( schemaName != null )
1493                        {
1494                            final int lastIndexOfSlash = schemaName.lastIndexOf( '/' );
1495                            if ( lastIndexOfSlash != -1 && lastIndexOfSlash < schemaName.length() )
1496                            {
1497                                schemaName = schemaName.substring( lastIndexOfSlash + 1 );
1498                            }
1499
1500                            for ( URI uri : getSchemaResources() )
1501                            {
1502                                if ( uri.getSchemeSpecificPart() != null
1503                                     && uri.getSchemeSpecificPart().endsWith( schemaName ) )
1504                                {
1505                                    schemaSource = new InputSource();
1506                                    schemaSource.setPublicId( publicId );
1507                                    schemaSource.setSystemId( uri.toASCIIString() );
1508
1509                                    if ( isLoggable( Level.FINEST ) )
1510                                    {
1511                                        log( Level.FINEST, getMessage( "resolutionInfo", systemUri.toASCIIString(),
1512                                                                       schemaSource.getSystemId() ), null );
1513
1514                                    }
1515
1516                                    break;
1517                                }
1518                            }
1519                        }
1520                        else
1521                        {
1522                            if ( isLoggable( Level.WARNING ) )
1523                            {
1524                                log( Level.WARNING, getMessage( "unsupportedIdUri", systemId,
1525                                                                systemUri.toASCIIString() ), null );
1526
1527                            }
1528
1529                            schemaSource = null;
1530                        }
1531                    }
1532                }
1533                catch ( final URISyntaxException e )
1534                {
1535                    if ( isLoggable( Level.WARNING ) )
1536                    {
1537                        log( Level.WARNING, getMessage( "unsupportedIdUri", systemId, getMessage( e ) ), null );
1538                    }
1539
1540                    schemaSource = null;
1541                }
1542                catch ( final ModelException e )
1543                {
1544                    String message = getMessage( e );
1545                    if ( message == null )
1546                    {
1547                        message = "";
1548                    }
1549                    else if ( message.length() > 0 )
1550                    {
1551                        message = " " + message;
1552                    }
1553
1554                    String resource = "";
1555                    if ( publicId != null )
1556                    {
1557                        resource = publicId + ", ";
1558                    }
1559                    resource += systemId;
1560
1561                    // JDK: As of JDK 6, "new IOException( message, cause )".
1562                    throw (IOException) new IOException( getMessage(
1563                        "failedResolving", resource, message ) ).initCause( e );
1564
1565                }
1566
1567                return schemaSource;
1568            }
1569
1570        };
1571    }
1572
1573    private LSResourceResolver createResourceResolver( final EntityResolver entityResolver )
1574    {
1575        if ( entityResolver == null )
1576        {
1577            throw new NullPointerException( "entityResolver" );
1578        }
1579
1580        return new LSResourceResolver()
1581        {
1582
1583            public LSInput resolveResource( final String type, final String namespaceURI, final String publicId,
1584                                            final String systemId, final String baseURI )
1585            {
1586                final String resolvePublicId = namespaceURI == null ? publicId : namespaceURI;
1587                final String resolveSystemId = systemId == null ? "" : systemId;
1588
1589                try
1590                {
1591                    if ( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) )
1592                    {
1593                        final InputSource schemaSource =
1594                            entityResolver.resolveEntity( resolvePublicId, resolveSystemId );
1595
1596                        if ( schemaSource != null )
1597                        {
1598                            return new LSInput()
1599                            {
1600
1601                                public Reader getCharacterStream()
1602                                {
1603                                    return schemaSource.getCharacterStream();
1604                                }
1605
1606                                public void setCharacterStream( final Reader characterStream )
1607                                {
1608                                    if ( isLoggable( Level.WARNING ) )
1609                                    {
1610                                        log( Level.WARNING, getMessage(
1611                                            "unsupportedOperation", "setCharacterStream",
1612                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1613
1614                                    }
1615                                }
1616
1617                                public InputStream getByteStream()
1618                                {
1619                                    return schemaSource.getByteStream();
1620                                }
1621
1622                                public void setByteStream( final InputStream byteStream )
1623                                {
1624                                    if ( isLoggable( Level.WARNING ) )
1625                                    {
1626                                        log( Level.WARNING, getMessage(
1627                                            "unsupportedOperation", "setByteStream",
1628                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1629
1630                                    }
1631                                }
1632
1633                                public String getStringData()
1634                                {
1635                                    return null;
1636                                }
1637
1638                                public void setStringData( final String stringData )
1639                                {
1640                                    if ( isLoggable( Level.WARNING ) )
1641                                    {
1642                                        log( Level.WARNING, getMessage(
1643                                            "unsupportedOperation", "setStringData",
1644                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1645
1646                                    }
1647                                }
1648
1649                                public String getSystemId()
1650                                {
1651                                    return schemaSource.getSystemId();
1652                                }
1653
1654                                public void setSystemId( final String systemId )
1655                                {
1656                                    if ( isLoggable( Level.WARNING ) )
1657                                    {
1658                                        log( Level.WARNING, getMessage(
1659                                            "unsupportedOperation", "setSystemId",
1660                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1661
1662                                    }
1663                                }
1664
1665                                public String getPublicId()
1666                                {
1667                                    return schemaSource.getPublicId();
1668                                }
1669
1670                                public void setPublicId( final String publicId )
1671                                {
1672                                    if ( isLoggable( Level.WARNING ) )
1673                                    {
1674                                        log( Level.WARNING, getMessage(
1675                                            "unsupportedOperation", "setPublicId",
1676                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1677
1678                                    }
1679                                }
1680
1681                                public String getBaseURI()
1682                                {
1683                                    return baseURI;
1684                                }
1685
1686                                public void setBaseURI( final String baseURI )
1687                                {
1688                                    if ( isLoggable( Level.WARNING ) )
1689                                    {
1690                                        log( Level.WARNING, getMessage(
1691                                            "unsupportedOperation", "setBaseURI",
1692                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1693
1694                                    }
1695                                }
1696
1697                                public String getEncoding()
1698                                {
1699                                    return schemaSource.getEncoding();
1700                                }
1701
1702                                public void setEncoding( final String encoding )
1703                                {
1704                                    if ( isLoggable( Level.WARNING ) )
1705                                    {
1706                                        log( Level.WARNING, getMessage(
1707                                            "unsupportedOperation", "setEncoding",
1708                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1709
1710                                    }
1711                                }
1712
1713                                public boolean getCertifiedText()
1714                                {
1715                                    return false;
1716                                }
1717
1718                                public void setCertifiedText( final boolean certifiedText )
1719                                {
1720                                    if ( isLoggable( Level.WARNING ) )
1721                                    {
1722                                        log( Level.WARNING, getMessage(
1723                                            "unsupportedOperation", "setCertifiedText",
1724                                            DefaultModelContext.class.getName() + ".LSResourceResolver" ), null );
1725
1726                                    }
1727                                }
1728
1729                            };
1730                        }
1731
1732                    }
1733                    else if ( isLoggable( Level.WARNING ) )
1734                    {
1735                        log( Level.WARNING, getMessage( "unsupportedResourceType", type ), null );
1736                    }
1737                }
1738                catch ( final SAXException e )
1739                {
1740                    String message = getMessage( e );
1741                    if ( message == null && e.getException() != null )
1742                    {
1743                        message = getMessage( e.getException() );
1744                    }
1745                    if ( message == null )
1746                    {
1747                        message = "";
1748                    }
1749                    else if ( message.length() > 0 )
1750                    {
1751                        message = " " + message;
1752                    }
1753
1754                    String resource = "";
1755                    if ( resolvePublicId != null )
1756                    {
1757                        resource = resolvePublicId + ", ";
1758                    }
1759                    resource += resolveSystemId;
1760
1761                    if ( isLoggable( Level.SEVERE ) )
1762                    {
1763                        log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1764                    }
1765                }
1766                catch ( final IOException e )
1767                {
1768                    String message = getMessage( e );
1769                    if ( message == null )
1770                    {
1771                        message = "";
1772                    }
1773                    else if ( message.length() > 0 )
1774                    {
1775                        message = " " + message;
1776                    }
1777
1778                    String resource = "";
1779                    if ( resolvePublicId != null )
1780                    {
1781                        resource = resolvePublicId + ", ";
1782                    }
1783                    resource += resolveSystemId;
1784
1785                    if ( isLoggable( Level.SEVERE ) )
1786                    {
1787                        log( Level.SEVERE, getMessage( "failedResolving", resource, message ), e );
1788                    }
1789                }
1790
1791                return null;
1792            }
1793
1794        };
1795    }
1796
1797    private javax.xml.validation.Schema createSchema( final Schemas schemas, final EntityResolver entityResolver,
1798                                                      final LSResourceResolver resourceResolver, final String model,
1799                                                      final URI publicId ) throws ModelException
1800    {
1801        if ( entityResolver == null )
1802        {
1803            throw new NullPointerException( "entityResolver" );
1804        }
1805        if ( model != null && publicId != null )
1806        {
1807            throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1808        }
1809
1810        try
1811        {
1812            final SchemaFactory f = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
1813            final List<Source> sources = new ArrayList<Source>( schemas != null ? schemas.getSchema().size() : 0 );
1814
1815            if ( schemas != null )
1816            {
1817                for ( Schema s : schemas.getSchema() )
1818                {
1819                    final InputSource inputSource = entityResolver.resolveEntity( s.getPublicId(), s.getSystemId() );
1820
1821                    if ( inputSource != null )
1822                    {
1823                        sources.add( new SAXSource( inputSource ) );
1824                    }
1825                }
1826            }
1827
1828            if ( sources.isEmpty() )
1829            {
1830                if ( model != null )
1831                {
1832                    throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1833                }
1834                if ( publicId != null )
1835                {
1836                    throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1837                }
1838            }
1839
1840            f.setResourceResolver( resourceResolver );
1841            f.setErrorHandler( new ErrorHandler()
1842            {
1843                // See http://java.net/jira/browse/JAXP-66
1844
1845                public void warning( final SAXParseException e ) throws SAXException
1846                {
1847                    String message = getMessage( e );
1848                    if ( message == null && e.getException() != null )
1849                    {
1850                        message = getMessage( e.getException() );
1851                    }
1852
1853                    if ( isLoggable( Level.WARNING ) )
1854                    {
1855                        log( Level.WARNING, message, e );
1856                    }
1857                }
1858
1859                public void error( final SAXParseException e ) throws SAXException
1860                {
1861                    throw e;
1862                }
1863
1864                public void fatalError( final SAXParseException e ) throws SAXException
1865                {
1866                    throw e;
1867                }
1868
1869            } );
1870
1871            if ( this.isLoggable( Level.FINEST ) )
1872            {
1873                final StringBuilder schemaInfo = new StringBuilder( sources.size() * 50 );
1874
1875                for ( Source s : sources )
1876                {
1877                    schemaInfo.append( ", " ).append( s.getSystemId() );
1878                }
1879
1880                this.log( Level.FINEST, getMessage( "creatingSchema", schemaInfo.substring( 2 ) ), null );
1881            }
1882
1883            return f.newSchema( sources.toArray( new Source[ sources.size() ] ) );
1884        }
1885        catch ( final IOException e )
1886        {
1887            throw new ModelException( getMessage( e ), e );
1888        }
1889        catch ( final SAXException e )
1890        {
1891            String message = getMessage( e );
1892            if ( message == null && e.getException() != null )
1893            {
1894                message = getMessage( e.getException() );
1895            }
1896
1897            throw new ModelException( message, e );
1898        }
1899    }
1900
1901    private JAXBContext createContext( final Schemas schemas, final String model, final URI publicId )
1902        throws ModelException
1903    {
1904        if ( model != null && publicId != null )
1905        {
1906            throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1907        }
1908
1909        try
1910        {
1911            StringBuilder packageNames = null;
1912
1913            if ( schemas != null )
1914            {
1915                packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1916
1917                for ( Schema schema : schemas.getSchema() )
1918                {
1919                    if ( schema.getContextId() != null )
1920                    {
1921                        packageNames.append( ':' ).append( schema.getContextId() );
1922                    }
1923                }
1924            }
1925
1926            if ( packageNames == null || packageNames.length() == 0 )
1927            {
1928                if ( model != null )
1929                {
1930                    throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1931                }
1932                if ( publicId != null )
1933                {
1934                    throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1935                }
1936            }
1937
1938            if ( this.isLoggable( Level.FINEST ) )
1939            {
1940                this.log( Level.FINEST, getMessage( "creatingContext", packageNames.substring( 1 ) ), null );
1941            }
1942
1943            return JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() );
1944        }
1945        catch ( final JAXBException e )
1946        {
1947            String message = getMessage( e );
1948            if ( message == null && e.getLinkedException() != null )
1949            {
1950                message = getMessage( e.getLinkedException() );
1951            }
1952
1953            throw new ModelException( message, e );
1954        }
1955    }
1956
1957    private Marshaller createMarshaller( final Schemas schemas, final Services services, final String model,
1958                                         final URI publicId ) throws ModelException
1959    {
1960        if ( model != null && publicId != null )
1961        {
1962            throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
1963        }
1964
1965        try
1966        {
1967            StringBuilder packageNames = null;
1968            StringBuilder schemaLocation = null;
1969
1970            if ( schemas != null )
1971            {
1972                packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
1973                schemaLocation = new StringBuilder( schemas.getSchema().size() * 50 );
1974
1975                for ( Schema schema : schemas.getSchema() )
1976                {
1977                    if ( schema.getContextId() != null )
1978                    {
1979                        packageNames.append( ':' ).append( schema.getContextId() );
1980                    }
1981                    if ( schema.getPublicId() != null && schema.getSystemId() != null )
1982                    {
1983                        schemaLocation.append( ' ' ).append( schema.getPublicId() ).append( ' ' ).
1984                            append( schema.getSystemId() );
1985
1986                    }
1987                }
1988            }
1989
1990            if ( packageNames == null || packageNames.length() == 0 )
1991            {
1992                if ( model != null )
1993                {
1994                    throw new ModelException( getMessage( "missingSchemasForModel", model ) );
1995                }
1996                if ( publicId != null )
1997                {
1998                    throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
1999                }
2000            }
2001
2002            final Marshaller m =
2003                JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createMarshaller();
2004
2005            if ( schemaLocation != null && schemaLocation.length() != 0 )
2006            {
2007                m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation.substring( 1 ) );
2008            }
2009
2010            MarshallerListenerList listenerList = null;
2011
2012            if ( services != null )
2013            {
2014                for ( Service service : services.getServices( MARSHALLER_LISTENER_SERVICE ) )
2015                {
2016                    if ( listenerList == null )
2017                    {
2018                        listenerList = new MarshallerListenerList();
2019                    }
2020
2021                    listenerList.getListeners().add( this.createServiceObject( service, Marshaller.Listener.class ) );
2022                }
2023            }
2024
2025            if ( listenerList != null )
2026            {
2027                m.setListener( listenerList );
2028            }
2029
2030            if ( this.isLoggable( Level.FINEST ) )
2031            {
2032                if ( listenerList == null )
2033                {
2034                    this.log( Level.FINEST, getMessage( "creatingMarshaller", packageNames.substring( 1 ),
2035                                                        schemaLocation.substring( 1 ) ), null );
2036
2037                }
2038                else
2039                {
2040                    final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
2041
2042                    for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
2043                    {
2044                        b.append( ',' ).append( listenerList.getListeners().get( i ) );
2045                    }
2046
2047                    this.log( Level.FINEST, getMessage( "creatingMarshallerWithListeners", packageNames.substring( 1 ),
2048                                                        schemaLocation.substring( 1 ), b.substring( 1 ) ), null );
2049
2050                }
2051            }
2052
2053            return m;
2054        }
2055        catch ( final JAXBException e )
2056        {
2057            String message = getMessage( e );
2058            if ( message == null && e.getLinkedException() != null )
2059            {
2060                message = getMessage( e.getLinkedException() );
2061            }
2062
2063            throw new ModelException( message, e );
2064        }
2065    }
2066
2067    private Unmarshaller createUnmarshaller( final Schemas schemas, final Services services, final String model,
2068                                             final URI publicId ) throws ModelException
2069    {
2070        if ( model != null && publicId != null )
2071        {
2072            throw new IllegalArgumentException( "model=" + model + ", publicId=" + publicId.toASCIIString() );
2073        }
2074
2075        try
2076        {
2077            StringBuilder packageNames = null;
2078
2079            if ( schemas != null )
2080            {
2081                packageNames = new StringBuilder( schemas.getSchema().size() * 25 );
2082
2083                for ( Schema schema : schemas.getSchema() )
2084                {
2085                    if ( schema.getContextId() != null )
2086                    {
2087                        packageNames.append( ':' ).append( schema.getContextId() );
2088                    }
2089                }
2090            }
2091
2092            if ( packageNames == null || packageNames.length() == 0 )
2093            {
2094                if ( model != null )
2095                {
2096                    throw new ModelException( getMessage( "missingSchemasForModel", model ) );
2097                }
2098                if ( publicId != null )
2099                {
2100                    throw new ModelException( getMessage( "missingSchemasForPublicId", publicId ) );
2101                }
2102            }
2103
2104            final Unmarshaller u =
2105                JAXBContext.newInstance( packageNames.substring( 1 ), this.getClassLoader() ).createUnmarshaller();
2106
2107            UnmarshallerListenerList listenerList = null;
2108
2109            if ( services != null )
2110            {
2111                for ( Service service : services.getServices( UNMARSHALLER_LISTENER_SERVICE ) )
2112                {
2113                    if ( listenerList == null )
2114                    {
2115                        listenerList = new UnmarshallerListenerList();
2116                    }
2117
2118                    listenerList.getListeners().add( this.createServiceObject( service, Unmarshaller.Listener.class ) );
2119                }
2120            }
2121
2122            if ( listenerList != null )
2123            {
2124                u.setListener( listenerList );
2125            }
2126
2127            if ( this.isLoggable( Level.FINEST ) )
2128            {
2129                if ( listenerList == null )
2130                {
2131                    this.log( Level.FINEST,
2132                              getMessage( "creatingUnmarshaller", packageNames.substring( 1 ) ), null );
2133
2134                }
2135                else
2136                {
2137                    final StringBuilder b = new StringBuilder( listenerList.getListeners().size() * 100 );
2138
2139                    for ( int i = 0, s0 = listenerList.getListeners().size(); i < s0; i++ )
2140                    {
2141                        b.append( ',' ).append( listenerList.getListeners().get( i ) );
2142                    }
2143
2144                    this.log( Level.FINEST, getMessage( "creatingUnmarshallerWithListeners",
2145                                                        packageNames.substring( 1 ), b.substring( 1 ) ), null );
2146
2147                }
2148            }
2149
2150            return u;
2151        }
2152        catch ( final JAXBException e )
2153        {
2154            String message = getMessage( e );
2155            if ( message == null && e.getLinkedException() != null )
2156            {
2157                message = getMessage( e.getLinkedException() );
2158            }
2159
2160            throw new ModelException( message, e );
2161        }
2162    }
2163
2164    private static String getMessage( final String key, final Object... arguments )
2165    {
2166        return MessageFormat.format( ResourceBundle.getBundle(
2167            DefaultModelContext.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2168
2169    }
2170
2171    private static String getMessage( final Throwable t )
2172    {
2173        return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2174    }
2175
2176}
2177
2178/**
2179 * {@code ErrorHandler} collecting {@code ModelValidationReport} details.
2180 *
2181 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2182 * @version $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
2183 */
2184class ModelErrorHandler extends DefaultHandler
2185{
2186
2187    /** The context of the instance. */
2188    private ModelContext context;
2189
2190    /** The report of the instance. */
2191    private ModelValidationReport report;
2192
2193    /**
2194     * Creates a new {@code ModelErrorHandler} instance taking a context.
2195     *
2196     * @param context The context of the instance.
2197     */
2198    ModelErrorHandler( final ModelContext context )
2199    {
2200        this( context, null );
2201    }
2202
2203    /**
2204     * Creates a new {@code ModelErrorHandler} instance taking a report to use for collecting validation events.
2205     *
2206     * @param context The context of the instance.
2207     * @param report A report to use for collecting validation events.
2208     */
2209    ModelErrorHandler( final ModelContext context, final ModelValidationReport report )
2210    {
2211        super();
2212        this.context = context;
2213        this.report = report;
2214    }
2215
2216    /**
2217     * Gets the report of the instance.
2218     *
2219     * @return The report of the instance.
2220     */
2221    public ModelValidationReport getReport()
2222    {
2223        if ( this.report == null )
2224        {
2225            this.report = new ModelValidationReport();
2226        }
2227
2228        return this.report;
2229    }
2230
2231    @Override
2232    public void warning( final SAXParseException exception ) throws SAXException
2233    {
2234        String message = getMessage( exception );
2235        if ( message == null && exception.getException() != null )
2236        {
2237            message = getMessage( exception.getException() );
2238        }
2239
2240        if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2241        {
2242            this.context.log( Level.FINE, message, exception );
2243        }
2244
2245        this.getReport().getDetails().add( new ModelValidationReport.Detail(
2246            "W3C XML 1.0 Recommendation - Warning condition", Level.WARNING, message, null ) );
2247
2248    }
2249
2250    @Override
2251    public void error( final SAXParseException exception ) throws SAXException
2252    {
2253        String message = getMessage( exception );
2254        if ( message == null && exception.getException() != null )
2255        {
2256            message = getMessage( exception.getException() );
2257        }
2258
2259        if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2260        {
2261            this.context.log( Level.FINE, message, exception );
2262        }
2263
2264        this.getReport().getDetails().add( new ModelValidationReport.Detail(
2265            "W3C XML 1.0 Recommendation - Section 1.2 - Error", Level.SEVERE, message, null ) );
2266
2267    }
2268
2269    @Override
2270    public void fatalError( final SAXParseException exception ) throws SAXException
2271    {
2272        String message = getMessage( exception );
2273        if ( message == null && exception.getException() != null )
2274        {
2275            message = getMessage( exception.getException() );
2276        }
2277
2278        if ( this.context != null && this.context.isLoggable( Level.FINE ) )
2279        {
2280            this.context.log( Level.FINE, message, exception );
2281        }
2282
2283        this.getReport().getDetails().add( new ModelValidationReport.Detail(
2284            "W3C XML 1.0 Recommendation - Section 1.2 - Fatal Error", Level.SEVERE, message, null ) );
2285
2286    }
2287
2288    private static String getMessage( final Throwable t )
2289    {
2290        return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2291    }
2292
2293}
2294
2295/**
2296 * List of {@code Marshaller.Listener}s.
2297 *
2298 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2299 * @version $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
2300 * @since 1.2
2301 */
2302class MarshallerListenerList extends Marshaller.Listener
2303{
2304
2305    /** The {@code Marshaller.Listener}s of the instance. */
2306    private List<Marshaller.Listener> listeners;
2307
2308    /** Creates a new {@code MarshallerListenerList} instance. */
2309    MarshallerListenerList()
2310    {
2311        super();
2312    }
2313
2314    /**
2315     * Gets the listeners of the instance.
2316     * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2317     * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2318     * listeners property.</p>
2319     *
2320     * @return The list of listeners of the instance.
2321     */
2322    List<Marshaller.Listener> getListeners()
2323    {
2324        if ( this.listeners == null )
2325        {
2326            this.listeners = new ArrayList<Marshaller.Listener>();
2327        }
2328
2329        return this.listeners;
2330    }
2331
2332    @Override
2333    public void beforeMarshal( final Object source )
2334    {
2335        for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2336        {
2337            this.getListeners().get( i ).beforeMarshal( source );
2338        }
2339    }
2340
2341    @Override
2342    public void afterMarshal( final Object source )
2343    {
2344        for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2345        {
2346            this.getListeners().get( i ).afterMarshal( source );
2347        }
2348    }
2349
2350}
2351
2352/**
2353 * List of {@code Unmarshaller.Listener}s.
2354 *
2355 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
2356 * @version $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
2357 * @since 1.2
2358 */
2359class UnmarshallerListenerList extends Unmarshaller.Listener
2360{
2361
2362    /** The {@code Unmarshaller.Listener}s of the instance. */
2363    private List<Unmarshaller.Listener> listeners;
2364
2365    /** Creates a new {@code UnmarshallerListenerList} instance. */
2366    UnmarshallerListenerList()
2367    {
2368        super();
2369    }
2370
2371    /**
2372     * Gets the listeners of the instance.
2373     * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
2374     * to the returned list will be present inside the object. This is why there is no {@code set} method for the
2375     * listeners property.</p>
2376     *
2377     * @return The list of listeners of the instance.
2378     */
2379    List<Unmarshaller.Listener> getListeners()
2380    {
2381        if ( this.listeners == null )
2382        {
2383            this.listeners = new ArrayList<Unmarshaller.Listener>();
2384        }
2385
2386        return this.listeners;
2387    }
2388
2389    @Override
2390    public void beforeUnmarshal( final Object target, final Object parent )
2391    {
2392        for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2393        {
2394            this.getListeners().get( i ).beforeUnmarshal( target, parent );
2395        }
2396    }
2397
2398    @Override
2399    public void afterUnmarshal( final Object target, final Object parent )
2400    {
2401        for ( int i = 0, s0 = this.getListeners().size(); i < s0; i++ )
2402        {
2403            this.getListeners().get( i ).afterUnmarshal( target, parent );
2404        }
2405    }
2406
2407}