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