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: ProjectClassLoader.java 4613 2012-09-22 10:07:08Z schulte $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.Closeable;
034import java.io.File;
035import java.io.FileOutputStream;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.OutputStream;
039import java.net.MalformedURLException;
040import java.net.URL;
041import java.net.URLClassLoader;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.Enumeration;
045import java.util.HashSet;
046import java.util.Iterator;
047import java.util.List;
048import java.util.Set;
049import javax.xml.bind.JAXBElement;
050import javax.xml.bind.JAXBException;
051import org.apache.commons.io.IOUtils;
052import org.apache.commons.lang.StringUtils;
053import org.apache.tools.ant.Project;
054import org.apache.tools.ant.types.Path;
055import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
056import org.jomc.modlet.ModelContext;
057import org.jomc.modlet.ModelContextFactory;
058import org.jomc.modlet.ModelException;
059import org.jomc.modlet.Modlet;
060import org.jomc.modlet.ModletObject;
061import org.jomc.modlet.Modlets;
062import org.jomc.modlet.ObjectFactory;
063import org.jomc.modlet.Schema;
064import org.jomc.modlet.Schemas;
065import org.jomc.modlet.Service;
066import org.jomc.modlet.Services;
067import org.jomc.util.ParseException;
068import org.jomc.util.TokenMgrError;
069import org.jomc.util.VersionParser;
070
071/**
072 * Class loader supporting JOMC resources backed by a project.
073 *
074 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
075 * @version $JOMC: ProjectClassLoader.java 4613 2012-09-22 10:07:08Z schulte $
076 */
077public class ProjectClassLoader extends URLClassLoader
078{
079
080    /** Constant to prefix relative resource names with. */
081    private static final String ABSOLUTE_RESOURCE_NAME_PREFIX = "/org/jomc/ant/";
082
083    /** Empty URL array. */
084    private static final URL[] NO_URLS =
085    {
086    };
087
088    /** Set of modlet names to exclude. */
089    private Set<String> modletExcludes;
090
091    /** Excluded modlets. */
092    private Modlets excludedModlets;
093
094    /** Set of service class names to exclude. */
095    private Set<String> serviceExcludes;
096
097    /** Excluded services. */
098    private Services excludedServices;
099
100    /** Set of schema public ids to exclude. */
101    private Set<String> schemaExcludes;
102
103    /** Excluded schemas. */
104    private Schemas excludedSchemas;
105
106    /** Set of providers to exclude. */
107    private Set<String> providerExcludes;
108
109    /** Set of excluded providers. */
110    private Set<String> excludedProviders;
111
112    /** The project the class loader is associated with. */
113    private final Project project;
114
115    /** Set of modlet resource locations to filter. */
116    private Set<String> modletResourceLocations;
117
118    /** Set of provider resource locations to filter. */
119    private Set<String> providerResourceLocations;
120
121    /** Set of temporary resources. */
122    private final Set<File> temporaryResources = new HashSet<File>();
123
124    /**
125     * Creates a new {@code ProjectClassLoader} instance taking a project and a class path.
126     *
127     * @param project The project to which this class loader is to belong.
128     * @param classpath The class path to use for loading.
129     *
130     * @throws MalformedURLException if {@code classpath} contains unsupported elements.
131     */
132    public ProjectClassLoader( final Project project, final Path classpath ) throws MalformedURLException
133    {
134        super( NO_URLS, ProjectClassLoader.class.getClassLoader() );
135
136        for ( final String name : classpath.list() )
137        {
138            final File resolved = project.resolveFile( name );
139            this.addURL( resolved.toURI().toURL() );
140        }
141
142        this.project = project;
143    }
144
145    /**
146     * Gets the project of the instance.
147     *
148     * @return The project of the instance.
149     */
150    public final Project getProject()
151    {
152        return this.project;
153    }
154
155    /**
156     * Finds a resource with a given name.
157     *
158     * @param name The name of the resource to search.
159     *
160     * @return An {@code URL} object for reading the resource or {@code null}, if no resource matching {@code name} is
161     * found.
162     */
163    @Override
164    public URL findResource( final String name )
165    {
166        try
167        {
168            URL resource = super.findResource( name );
169
170            if ( resource != null )
171            {
172                if ( this.getProviderResourceLocations().contains( name ) )
173                {
174                    resource = this.filterProviders( resource );
175                }
176                else if ( this.getModletResourceLocations().contains( name ) )
177                {
178                    resource = this.filterModlets( resource );
179                }
180            }
181
182            return resource;
183        }
184        catch ( final IOException e )
185        {
186            this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
187            return null;
188        }
189        catch ( final JAXBException e )
190        {
191            String message = Messages.getMessage( e );
192            if ( message == null && e.getLinkedException() != null )
193            {
194                message = Messages.getMessage( e.getLinkedException() );
195            }
196
197            this.getProject().log( message, Project.MSG_ERR );
198            return null;
199        }
200        catch ( final ModelException e )
201        {
202            this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
203            return null;
204        }
205    }
206
207    /**
208     * Gets all resources matching a given name.
209     *
210     * @param name The name of the resources to get.
211     *
212     * @return An enumeration of {@code URL} objects of found resources.
213     *
214     * @throws IOException if getting resources fails.
215     */
216    @Override
217    public Enumeration<URL> findResources( final String name ) throws IOException
218    {
219        final Enumeration<URL> allResources = super.findResources( name );
220        Enumeration<URL> enumeration = allResources;
221
222        if ( this.getProviderResourceLocations().contains( name ) )
223        {
224            enumeration = new Enumeration<URL>()
225            {
226
227                public boolean hasMoreElements()
228                {
229                    return allResources.hasMoreElements();
230                }
231
232                public URL nextElement()
233                {
234                    try
235                    {
236                        return filterProviders( allResources.nextElement() );
237                    }
238                    catch ( final IOException e )
239                    {
240                        getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
241                        return null;
242                    }
243                }
244
245            };
246        }
247        else if ( this.getModletResourceLocations().contains( name ) )
248        {
249            enumeration = new Enumeration<URL>()
250            {
251
252                public boolean hasMoreElements()
253                {
254                    return allResources.hasMoreElements();
255                }
256
257                public URL nextElement()
258                {
259                    try
260                    {
261                        return filterModlets( allResources.nextElement() );
262                    }
263                    catch ( final IOException e )
264                    {
265                        getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
266                        return null;
267                    }
268                    catch ( final JAXBException e )
269                    {
270                        String message = Messages.getMessage( e );
271                        if ( message == null && e.getLinkedException() != null )
272                        {
273                            message = Messages.getMessage( e.getLinkedException() );
274                        }
275
276                        getProject().log( message, Project.MSG_ERR );
277                        return null;
278                    }
279                    catch ( final ModelException e )
280                    {
281                        getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
282                        return null;
283                    }
284                }
285
286            };
287        }
288
289        return enumeration;
290    }
291
292    /**
293     * Gets a set of modlet resource locations to filter.
294     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
295     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
296     * modlet resource locations property.</p>
297     *
298     * @return A set of modlet resource locations to filter.
299     */
300    public final Set<String> getModletResourceLocations()
301    {
302        if ( this.modletResourceLocations == null )
303        {
304            this.modletResourceLocations = new HashSet<String>();
305        }
306
307        return this.modletResourceLocations;
308    }
309
310    /**
311     * Gets a set of provider resource locations to filter.
312     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
313     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
314     * provider resource locations property.</p>
315     *
316     * @return A set of provider resource locations to filter.
317     */
318    public final Set<String> getProviderResourceLocations()
319    {
320        if ( this.providerResourceLocations == null )
321        {
322            this.providerResourceLocations = new HashSet<String>();
323        }
324
325        return this.providerResourceLocations;
326    }
327
328    /**
329     * Gets a set of modlet names to exclude.
330     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
331     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
332     * modlet excludes property.</p>
333     *
334     * @return A set of modlet names to exclude.
335     */
336    public final Set<String> getModletExcludes()
337    {
338        if ( this.modletExcludes == null )
339        {
340            this.modletExcludes = new HashSet<String>();
341        }
342
343        return this.modletExcludes;
344    }
345
346    /**
347     * Gets a set of modlet names excluded by default.
348     *
349     * @return An unmodifiable set of modlet names excluded by default.
350     *
351     * @throws IOException if reading configuration resources fails.
352     */
353    public static Set<String> getDefaultModletExcludes() throws IOException
354    {
355        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultModletExcludes" );
356    }
357
358    /**
359     * Gets a set of modlets excluded during resource loading.
360     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
361     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
362     * excluded modlets property.</p>
363     *
364     * @return A set of modlets excluded during resource loading.
365     */
366    public final Modlets getExcludedModlets()
367    {
368        if ( this.excludedModlets == null )
369        {
370            this.excludedModlets = new Modlets();
371        }
372
373        return this.excludedModlets;
374    }
375
376    /**
377     * Gets a set of provider names to exclude.
378     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
379     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
380     * provider excludes property.</p>
381     *
382     * @return A set of providers to exclude.
383     */
384    public final Set<String> getProviderExcludes()
385    {
386        if ( this.providerExcludes == null )
387        {
388            this.providerExcludes = new HashSet<String>();
389        }
390
391        return this.providerExcludes;
392    }
393
394    /**
395     * Gets a set of provider names excluded by default.
396     *
397     * @return An unmodifiable set of provider names excluded by default.
398     *
399     * @throws IOException if reading configuration resources fails.
400     */
401    public static Set<String> getDefaultProviderExcludes() throws IOException
402    {
403        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultProviderExcludes" );
404    }
405
406    /**
407     * Gets a set of providers excluded during resource loading.
408     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
409     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
410     * excluded providers property.</p>
411     *
412     * @return A set of providers excluded during resource loading.
413     */
414    public final Set<String> getExcludedProviders()
415    {
416        if ( this.excludedProviders == null )
417        {
418            this.excludedProviders = new HashSet<String>();
419        }
420
421        return this.excludedProviders;
422    }
423
424    /**
425     * Gets a set of service class names to exclude.
426     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
427     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
428     * service excludes property.</p>
429     *
430     * @return A set of service class names to exclude.
431     */
432    public final Set<String> getServiceExcludes()
433    {
434        if ( this.serviceExcludes == null )
435        {
436            this.serviceExcludes = new HashSet<String>();
437        }
438
439        return this.serviceExcludes;
440    }
441
442    /**
443     * Gets a set of service class names excluded by default.
444     *
445     * @return An unmodifiable set of service class names excluded by default.
446     *
447     * @throws IOException if reading configuration resources fails.
448     */
449    public static Set<String> getDefaultServiceExcludes() throws IOException
450    {
451        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultServiceExcludes" );
452    }
453
454    /**
455     * Gets a set of services excluded during resource loading.
456     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
457     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
458     * excluded services property.</p>
459     *
460     * @return Services excluded during resource loading.
461     */
462    public final Services getExcludedServices()
463    {
464        if ( this.excludedServices == null )
465        {
466            this.excludedServices = new Services();
467        }
468
469        return this.excludedServices;
470    }
471
472    /**
473     * Gets a set of schema public identifiers to exclude.
474     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
475     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
476     * schema excludes property.</p>
477     *
478     * @return A set of schema public identifiers to exclude.
479     */
480    public final Set<String> getSchemaExcludes()
481    {
482        if ( this.schemaExcludes == null )
483        {
484            this.schemaExcludes = new HashSet<String>();
485        }
486
487        return this.schemaExcludes;
488    }
489
490    /**
491     * Gets a set of schema public identifiers excluded by default.
492     *
493     * @return An unmodifiable set of schema public identifiers excluded by default.
494     *
495     * @throws IOException if reading configuration resources fails.
496     */
497    public static Set<String> getDefaultSchemaExcludes() throws IOException
498    {
499        return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultSchemaExcludes" );
500    }
501
502    /**
503     * Gets a set of schemas excluded during resource loading.
504     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
505     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
506     * excluded schemas property.</p>
507     *
508     * @return Schemas excluded during resource loading.
509     */
510    public final Schemas getExcludedSchemas()
511    {
512        if ( this.excludedSchemas == null )
513        {
514            this.excludedSchemas = new Schemas();
515        }
516
517        return this.excludedSchemas;
518    }
519
520    /**
521     * Closes the class loader.
522     * @throws IOException if closing the class loader fails.
523     */
524    @Override
525    @IgnoreJRERequirement
526    public void close() throws IOException
527    {
528        for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
529        {
530            final File temporaryResource = it.next();
531
532            if ( temporaryResource.exists() && temporaryResource.delete() )
533            {
534                it.remove();
535            }
536        }
537
538        if ( Closeable.class.isAssignableFrom( ProjectClassLoader.class ) )
539        {
540            super.close();
541        }
542    }
543
544    /**
545     * Removes temporary resources.
546     * @throws Throwable if finalization fails.
547     */
548    @Override
549    protected void finalize() throws Throwable
550    {
551        for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
552        {
553            final File temporaryResource = it.next();
554
555            if ( temporaryResource.exists() && !temporaryResource.delete() )
556            {
557                temporaryResource.deleteOnExit();
558            }
559
560            it.remove();
561        }
562
563        super.finalize();
564    }
565
566    private URL filterProviders( final URL resource ) throws IOException
567    {
568        InputStream in = null;
569        boolean suppressExceptionOnClose = true;
570
571        try
572        {
573            URL filteredResource = resource;
574            in = resource.openStream();
575            final List<String> lines = IOUtils.readLines( in, "UTF-8" );
576            final List<String> filteredLines = new ArrayList<String>( lines.size() );
577
578            for ( String line : lines )
579            {
580                if ( !this.getProviderExcludes().contains( line.trim() ) )
581                {
582                    filteredLines.add( line.trim() );
583                }
584                else
585                {
586                    this.getExcludedProviders().add( line.trim() );
587                    this.getProject().log( Messages.getMessage( "providerExclusion", resource.toExternalForm(),
588                                                                line.trim() ), Project.MSG_DEBUG );
589
590                }
591            }
592
593            if ( lines.size() != filteredLines.size() )
594            {
595                OutputStream out = null;
596                final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
597                this.temporaryResources.add( tmpResource );
598
599                try
600                {
601                    out = new FileOutputStream( tmpResource );
602                    IOUtils.writeLines( filteredLines, System.getProperty( "line.separator", "\n" ), out, "UTF-8" );
603                    suppressExceptionOnClose = false;
604                }
605                finally
606                {
607                    try
608                    {
609                        if ( out != null )
610                        {
611                            out.close();
612                        }
613
614                        suppressExceptionOnClose = true;
615                    }
616                    catch ( final IOException e )
617                    {
618                        if ( suppressExceptionOnClose )
619                        {
620                            this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
621                        }
622                        else
623                        {
624                            throw e;
625                        }
626                    }
627                }
628
629                filteredResource = tmpResource.toURI().toURL();
630            }
631
632            suppressExceptionOnClose = false;
633            return filteredResource;
634        }
635        finally
636        {
637            try
638            {
639                if ( in != null )
640                {
641                    in.close();
642                }
643            }
644            catch ( final IOException e )
645            {
646                if ( suppressExceptionOnClose )
647                {
648                    this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
649                }
650                else
651                {
652                    throw e;
653                }
654            }
655        }
656    }
657
658    private URL filterModlets( final URL resource ) throws ModelException, IOException, JAXBException
659    {
660        InputStream in = null;
661        boolean suppressExceptionOnClose = true;
662
663        try
664        {
665            URL filteredResource = resource;
666            final ModelContext modelContext = ModelContextFactory.newInstance().newModelContext();
667            in = resource.openStream();
668            final JAXBElement<?> e =
669                (JAXBElement<?>) modelContext.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID ).unmarshal( in );
670
671            final Object o = e.getValue();
672            Modlets modlets = null;
673            boolean filtered = false;
674
675            if ( o instanceof Modlets )
676            {
677                modlets = (Modlets) o;
678            }
679            else if ( o instanceof Modlet )
680            {
681                modlets = new Modlets();
682                modlets.getModlet().add( (Modlet) o );
683            }
684
685            if ( modlets != null )
686            {
687                for ( final Iterator<Modlet> it = modlets.getModlet().iterator(); it.hasNext(); )
688                {
689                    final Modlet m = it.next();
690
691                    if ( this.getModletExcludes().contains( m.getName() ) )
692                    {
693                        it.remove();
694                        filtered = true;
695                        this.addExcludedModlet( m );
696                        this.getProject().log( Messages.getMessage( "modletExclusion", resource.toExternalForm(),
697                                                                    m.getName() ), Project.MSG_DEBUG );
698
699                        continue;
700                    }
701
702                    if ( this.filterModlet( m, resource.toExternalForm() ) )
703                    {
704                        filtered = true;
705                    }
706                }
707
708                if ( filtered )
709                {
710                    final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
711                    this.temporaryResources.add( tmpResource );
712                    modelContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID ).marshal(
713                        new ObjectFactory().createModlets( modlets ), tmpResource );
714
715                    filteredResource = tmpResource.toURI().toURL();
716                }
717            }
718
719            suppressExceptionOnClose = false;
720            return filteredResource;
721        }
722        finally
723        {
724            try
725            {
726                if ( in != null )
727                {
728                    in.close();
729                }
730            }
731            catch ( final IOException e )
732            {
733                if ( suppressExceptionOnClose )
734                {
735                    this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
736                }
737                else
738                {
739                    throw e;
740                }
741            }
742        }
743    }
744
745    private boolean filterModlet( final Modlet modlet, final String resourceInfo )
746    {
747        boolean filteredSchemas = false;
748        boolean filteredServices = false;
749
750        if ( modlet.getSchemas() != null )
751        {
752            final Schemas schemas = new Schemas();
753
754            for ( Schema s : modlet.getSchemas().getSchema() )
755            {
756                if ( !this.getSchemaExcludes().contains( s.getPublicId() ) )
757                {
758                    schemas.getSchema().add( s );
759                }
760                else
761                {
762                    this.getProject().log( Messages.getMessage( "schemaExclusion", resourceInfo, s.getPublicId() ),
763                                           Project.MSG_DEBUG );
764
765                    this.addExcludedSchema( s );
766                    filteredSchemas = true;
767                }
768            }
769
770            if ( filteredSchemas )
771            {
772                modlet.setSchemas( schemas );
773            }
774        }
775
776        if ( modlet.getServices() != null )
777        {
778            final Services services = new Services();
779
780            for ( Service s : modlet.getServices().getService() )
781            {
782                if ( !this.getServiceExcludes().contains( s.getClazz() ) )
783                {
784                    services.getService().add( s );
785                }
786                else
787                {
788                    this.getProject().log( Messages.getMessage( "serviceExclusion", resourceInfo, s.getClazz() ),
789                                           Project.MSG_DEBUG );
790
791                    this.addExcludedService( s );
792                    filteredServices = true;
793                }
794            }
795
796            if ( filteredServices )
797            {
798                modlet.setServices( services );
799            }
800        }
801
802        return filteredSchemas || filteredServices;
803    }
804
805    private void addExcludedModlet( final Modlet modlet )
806    {
807        try
808        {
809            final Modlet m = this.getExcludedModlets().getModlet( modlet.getName() );
810
811            if ( m != null )
812            {
813                if ( m.getVersion() != null && modlet.getVersion() != null
814                     && VersionParser.compare( m.getVersion(), modlet.getVersion() ) < 0 )
815                {
816                    this.getExcludedModlets().getModlet().remove( m );
817                    this.getExcludedModlets().getModlet().add( modlet );
818                }
819            }
820            else
821            {
822                this.getExcludedModlets().getModlet().add( modlet );
823            }
824        }
825        catch ( final ParseException e )
826        {
827            this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
828        }
829        catch ( final TokenMgrError e )
830        {
831            this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
832        }
833    }
834
835    private void addExcludedSchema( final Schema schema )
836    {
837        if ( this.getExcludedSchemas().getSchemaBySystemId( schema.getSystemId() ) == null )
838        {
839            this.getExcludedSchemas().getSchema().add( schema );
840        }
841    }
842
843    private void addExcludedService( final Service service )
844    {
845        for ( int i = 0, s0 = this.getExcludedServices().getService().size(); i < s0; i++ )
846        {
847            final Service s = this.getExcludedServices().getService().get( i );
848
849            if ( s.getIdentifier().equals( service.getIdentifier() ) && s.getClazz().equals( service.getClazz() ) )
850            {
851                return;
852            }
853        }
854
855        this.getExcludedServices().getService().add( service );
856    }
857
858    private static Set<String> readDefaultExcludes( final String location ) throws IOException
859    {
860        InputStream resource = null;
861        boolean suppressExceptionOnClose = true;
862        Set<String> defaultExcludes = null;
863
864        try
865        {
866            resource = ProjectClassLoader.class.getResourceAsStream( location );
867
868            if ( resource != null )
869            {
870                final List<String> lines = IOUtils.readLines( resource, "UTF-8" );
871                defaultExcludes = new HashSet<String>( lines.size() );
872
873                for ( String line : lines )
874                {
875                    final String trimmed = line.trim();
876
877                    if ( trimmed.contains( "#" ) || StringUtils.isEmpty( trimmed ) )
878                    {
879                        continue;
880                    }
881
882                    defaultExcludes.add( trimmed );
883                }
884            }
885
886            suppressExceptionOnClose = false;
887            return defaultExcludes != null
888                   ? Collections.unmodifiableSet( defaultExcludes ) : Collections.<String>emptySet();
889
890        }
891        finally
892        {
893            try
894            {
895                if ( resource != null )
896                {
897                    resource.close();
898                }
899            }
900            catch ( final IOException e )
901            {
902                if ( !suppressExceptionOnClose )
903                {
904                    throw e;
905                }
906            }
907        }
908    }
909
910}