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