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