View Javadoc
1   /*
2    *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: ProjectClassLoader.java 5303 2016-08-30 02:31:20Z schulte $
29   *
30   */
31  package org.jomc.ant;
32  
33  import java.io.BufferedReader;
34  import java.io.BufferedWriter;
35  import java.io.Closeable;
36  import java.io.File;
37  import java.io.FileOutputStream;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.io.OutputStream;
42  import java.io.OutputStreamWriter;
43  import java.net.MalformedURLException;
44  import java.net.URI;
45  import java.net.URISyntaxException;
46  import java.net.URL;
47  import java.net.URLClassLoader;
48  import java.util.Collections;
49  import java.util.Enumeration;
50  import java.util.HashSet;
51  import java.util.Iterator;
52  import java.util.LinkedList;
53  import java.util.List;
54  import java.util.Set;
55  import javax.xml.bind.JAXBElement;
56  import javax.xml.bind.JAXBException;
57  import org.apache.tools.ant.Project;
58  import org.apache.tools.ant.types.Path;
59  import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
60  import org.jomc.modlet.ModelContext;
61  import org.jomc.modlet.ModelContextFactory;
62  import org.jomc.modlet.ModelException;
63  import org.jomc.modlet.Modlet;
64  import org.jomc.modlet.ModletObject;
65  import org.jomc.modlet.Modlets;
66  import org.jomc.modlet.ObjectFactory;
67  import org.jomc.modlet.Schema;
68  import org.jomc.modlet.Schemas;
69  import org.jomc.modlet.Service;
70  import org.jomc.modlet.Services;
71  import org.jomc.util.ParseException;
72  import org.jomc.util.TokenMgrError;
73  import org.jomc.util.VersionParser;
74  
75  /**
76   * Class loader supporting JOMC resources backed by a project.
77   *
78   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
79   * @version $JOMC: ProjectClassLoader.java 5303 2016-08-30 02:31:20Z schulte $
80   */
81  public class ProjectClassLoader extends URLClassLoader
82  {
83  
84      /**
85       * Constant to prefix relative resource names with.
86       */
87      private static final String ABSOLUTE_RESOURCE_NAME_PREFIX =
88          "/" + ProjectClassLoader.class.getPackage().getName().replace( '.', '/' ) + "/";
89  
90      /**
91       * Empty URL array.
92       */
93      private static final URL[] NO_URLS =
94      {
95      };
96  
97      /**
98       * Set of modlet names to exclude.
99       */
100     private Set<String> modletExcludes;
101 
102     /**
103      * Excluded modlets.
104      */
105     private final Modlets excludedModlets = new Modlets();
106 
107     /**
108      * Set of service class names to exclude.
109      */
110     private Set<String> serviceExcludes;
111 
112     /**
113      * Excluded services.
114      */
115     private final Services excludedServices = new Services();
116 
117     /**
118      * Set of schema public ids to exclude.
119      */
120     private Set<String> schemaExcludes;
121 
122     /**
123      * Excluded schemas.
124      */
125     private final Schemas excludedSchemas = new Schemas();
126 
127     /**
128      * Set of providers to exclude.
129      */
130     private Set<String> providerExcludes;
131 
132     /**
133      * Set of excluded providers.
134      */
135     private final Set<String> excludedProviders = Collections.synchronizedSet( new HashSet<String>( 128 ) );
136 
137     /**
138      * The project the class loader is associated with.
139      */
140     private final Project project;
141 
142     /**
143      * Set of modlet resource locations to filter.
144      */
145     private Set<String> modletResourceLocations;
146 
147     /**
148      * Set of provider resource locations to filter.
149      */
150     private Set<String> providerResourceLocations;
151 
152     /**
153      * Set of temporary resources.
154      */
155     private final Set<File> temporaryResources = Collections.synchronizedSet( new HashSet<File>( 128 ) );
156 
157     /**
158      * Creates a new {@code ProjectClassLoader} instance taking a project and a class path.
159      *
160      * @param project The project to which this class loader is to belong.
161      * @param classpath The class path to use for loading.
162      *
163      * @throws MalformedURLException if {@code classpath} contains unsupported elements.
164      * @throws IOException if reading configuration resources fails.
165      */
166     public ProjectClassLoader( final Project project, final Path classpath ) throws MalformedURLException, IOException
167     {
168         super( NO_URLS, ProjectClassLoader.class.getClassLoader() );
169 
170         for ( final String name : classpath.list() )
171         {
172             final File resolved = project.resolveFile( name );
173             this.addURL( resolved.toURI().toURL() );
174         }
175 
176         this.project = project;
177     }
178 
179     /**
180      * Gets the project of the instance.
181      *
182      * @return The project of the instance.
183      */
184     public final Project getProject()
185     {
186         return this.project;
187     }
188 
189     /**
190      * Finds a resource with a given name.
191      *
192      * @param name The name of the resource to search.
193      *
194      * @return An {@code URL} object for reading the resource or {@code null}, if no resource matching {@code name} is
195      * found.
196      */
197     @Override
198     public URL findResource( final String name ) //JDK: As of JDK 23 throws IOException
199     {
200         try
201         {
202             URL resource = super.findResource( name );
203 
204             if ( resource != null )
205             {
206                 if ( this.getProviderResourceLocations().contains( name ) )
207                 {
208                     resource = this.filterProviders( resource );
209                 }
210                 else if ( this.getModletResourceLocations().contains( name ) )
211                 {
212                     resource = this.filterModlets( resource );
213                 }
214             }
215 
216             return resource;
217         }
218         catch ( final IOException e )
219         {
220             this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
221             return null;
222         }
223         catch ( final JAXBException e )
224         {
225             String message = Messages.getMessage( e );
226             if ( message == null && e.getLinkedException() != null )
227             {
228                 message = Messages.getMessage( e.getLinkedException() );
229             }
230 
231             this.getProject().log( message, Project.MSG_ERR );
232             return null;
233         }
234         catch ( final ModelException e )
235         {
236             this.getProject().log( Messages.getMessage( e ), Project.MSG_ERR );
237             return null;
238         }
239     }
240 
241     /**
242      * Finds all resources matching a given name.
243      *
244      * @param name The name of the resources to search.
245      *
246      * @return An enumeration of {@code URL} objects of resources matching name.
247      *
248      * @throws IOException if getting resources fails.
249      */
250     @Override
251     public Enumeration<URL> findResources( final String name ) throws IOException
252     {
253         try
254         {
255             Enumeration<URL> resources = super.findResources( name );
256 
257             if ( this.getProviderResourceLocations().contains( name )
258                      || this.getModletResourceLocations().contains( name ) )
259             {
260                 final List<URI> filtered = new LinkedList<URI>();
261 
262                 while ( resources.hasMoreElements() )
263                 {
264                     final URL resource = resources.nextElement();
265 
266                     if ( this.getProviderResourceLocations().contains( name ) )
267                     {
268                         filtered.add( this.filterProviders( resource ).toURI() );
269                     }
270                     else if ( this.getModletResourceLocations().contains( name ) )
271                     {
272                         filtered.add( this.filterModlets( resource ).toURI() );
273                     }
274                 }
275 
276                 final Iterator<URI> it = filtered.iterator();
277 
278                 resources = new Enumeration<URL>()
279                 {
280 
281                     public boolean hasMoreElements()
282                     {
283                         return it.hasNext();
284                     }
285 
286                     public URL nextElement()
287                     {
288                         try
289                         {
290                             return it.next().toURL();
291                         }
292                         catch ( final MalformedURLException e )
293                         {
294                             throw new AssertionError( e );
295                         }
296                     }
297 
298                 };
299             }
300 
301             return resources;
302         }
303         catch ( final URISyntaxException e )
304         {
305             // JDK: As of JDK 6, new IOException( message, e );
306             throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
307         }
308         catch ( final JAXBException e )
309         {
310             String message = Messages.getMessage( e );
311             if ( message == null && e.getLinkedException() != null )
312             {
313                 message = Messages.getMessage( e.getLinkedException() );
314             }
315 
316             // JDK: As of JDK 6, new IOException( message, e );
317             throw (IOException) new IOException( message ).initCause( e );
318         }
319         catch ( final ModelException e )
320         {
321             // JDK: As of JDK 6, new IOException( message, e );
322             throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
323         }
324     }
325 
326     /**
327      * Gets a set of modlet resource locations to filter.
328      * <p>
329      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
330      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
331      * modlet resource locations property.
332      * </p>
333      *
334      * @return A set of modlet resource locations to filter.
335      */
336     public final Set<String> getModletResourceLocations()
337     {
338         if ( this.modletResourceLocations == null )
339         {
340             this.modletResourceLocations = new HashSet<String>( 128 );
341         }
342 
343         return this.modletResourceLocations;
344     }
345 
346     /**
347      * Gets a set of provider resource locations to filter.
348      * <p>
349      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
350      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
351      * provider resource locations property.
352      * </p>
353      *
354      * @return A set of provider resource locations to filter.
355      */
356     public final Set<String> getProviderResourceLocations()
357     {
358         if ( this.providerResourceLocations == null )
359         {
360             this.providerResourceLocations = new HashSet<String>( 128 );
361         }
362 
363         return this.providerResourceLocations;
364     }
365 
366     /**
367      * Gets a set of modlet names to exclude.
368      * <p>
369      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
370      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
371      * modlet excludes property.
372      * </p>
373      *
374      * @return A set of modlet names to exclude.
375      */
376     public final Set<String> getModletExcludes()
377     {
378         if ( this.modletExcludes == null )
379         {
380             this.modletExcludes = new HashSet<String>( 128 );
381         }
382 
383         return this.modletExcludes;
384     }
385 
386     /**
387      * Gets a set of modlet names excluded by default.
388      *
389      * @return An unmodifiable set of modlet names excluded by default.
390      *
391      * @throws IOException if reading configuration resources fails.
392      */
393     public static Set<String> getDefaultModletExcludes() throws IOException
394     {
395         return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultModletExcludes" );
396     }
397 
398     /**
399      * Gets a set of modlets excluded during resource loading.
400      * <p>
401      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
402      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
403      * excluded modlets property.
404      * </p>
405      *
406      * @return A set of modlets excluded during resource loading.
407      */
408     public final Modlets getExcludedModlets()
409     {
410         return this.excludedModlets;
411     }
412 
413     /**
414      * Gets a set of provider names to exclude.
415      * <p>
416      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
417      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
418      * provider excludes property.
419      * </p>
420      *
421      * @return A set of providers to exclude.
422      */
423     public final Set<String> getProviderExcludes()
424     {
425         if ( this.providerExcludes == null )
426         {
427             this.providerExcludes = new HashSet<String>( 128 );
428         }
429 
430         return this.providerExcludes;
431     }
432 
433     /**
434      * Gets a set of provider names excluded by default.
435      *
436      * @return An unmodifiable set of provider names excluded by default.
437      *
438      * @throws IOException if reading configuration resources fails.
439      */
440     public static Set<String> getDefaultProviderExcludes() throws IOException
441     {
442         return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultProviderExcludes" );
443     }
444 
445     /**
446      * Gets a set of providers excluded during resource loading.
447      * <p>
448      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
449      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
450      * excluded providers property.
451      * </p>
452      *
453      * @return A set of providers excluded during resource loading.
454      */
455     public final Set<String> getExcludedProviders()
456     {
457         return this.excludedProviders;
458     }
459 
460     /**
461      * Gets a set of service class names to exclude.
462      * <p>
463      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
464      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
465      * service excludes property.
466      * </p>
467      *
468      * @return A set of service class names to exclude.
469      */
470     public final Set<String> getServiceExcludes()
471     {
472         if ( this.serviceExcludes == null )
473         {
474             this.serviceExcludes = new HashSet<String>( 128 );
475         }
476 
477         return this.serviceExcludes;
478     }
479 
480     /**
481      * Gets a set of service class names excluded by default.
482      *
483      * @return An unmodifiable set of service class names excluded by default.
484      *
485      * @throws IOException if reading configuration resources fails.
486      */
487     public static Set<String> getDefaultServiceExcludes() throws IOException
488     {
489         return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultServiceExcludes" );
490     }
491 
492     /**
493      * Gets a set of services excluded during resource loading.
494      * <p>
495      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
496      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
497      * excluded services property.
498      * </p>
499      *
500      * @return Services excluded during resource loading.
501      */
502     public final Services getExcludedServices()
503     {
504         return this.excludedServices;
505     }
506 
507     /**
508      * Gets a set of schema public identifiers to exclude.
509      * <p>
510      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
511      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
512      * schema excludes property.
513      * </p>
514      *
515      * @return A set of schema public identifiers to exclude.
516      */
517     public final Set<String> getSchemaExcludes()
518     {
519         if ( this.schemaExcludes == null )
520         {
521             this.schemaExcludes = new HashSet<String>( 128 );
522         }
523 
524         return this.schemaExcludes;
525     }
526 
527     /**
528      * Gets a set of schema public identifiers excluded by default.
529      *
530      * @return An unmodifiable set of schema public identifiers excluded by default.
531      *
532      * @throws IOException if reading configuration resources fails.
533      */
534     public static Set<String> getDefaultSchemaExcludes() throws IOException
535     {
536         return readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultSchemaExcludes" );
537     }
538 
539     /**
540      * Gets a set of schemas excluded during resource loading.
541      * <p>
542      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
543      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
544      * excluded schemas property.
545      * </p>
546      *
547      * @return Schemas excluded during resource loading.
548      */
549     public final Schemas getExcludedSchemas()
550     {
551         return this.excludedSchemas;
552     }
553 
554     /**
555      * Closes the class loader.
556      *
557      * @throws IOException if closing the class loader fails.
558      */
559     @Override
560     @IgnoreJRERequirement
561     public void close() throws IOException
562     {
563         for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
564         {
565             final File temporaryResource = it.next();
566 
567             if ( temporaryResource.exists() && temporaryResource.delete() )
568             {
569                 it.remove();
570             }
571         }
572 
573         if ( Closeable.class.isAssignableFrom( ProjectClassLoader.class ) )
574         {
575             super.close();
576         }
577     }
578 
579     /**
580      * Removes temporary resources.
581      *
582      * @throws Throwable if finalization fails.
583      */
584     @Override
585     protected void finalize() throws Throwable
586     {
587         for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
588         {
589             final File temporaryResource = it.next();
590 
591             if ( temporaryResource.exists() && !temporaryResource.delete() )
592             {
593                 temporaryResource.deleteOnExit();
594             }
595 
596             it.remove();
597         }
598 
599         super.finalize();
600     }
601 
602     private URL filterProviders( final URL resource ) throws IOException
603     {
604         InputStream in = null;
605         BufferedReader reader = null;
606         OutputStream out = null;
607         BufferedWriter writer = null;
608         URL filteredResource = resource;
609         final List<String> filteredLines = new LinkedList<String>();
610 
611         try
612         {
613             boolean filtered = false;
614             in = resource.openStream();
615             reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
616 
617             for ( String line = reader.readLine(); line != null; line = reader.readLine() )
618             {
619                 String normalized = line.trim();
620 
621                 if ( !this.getProviderExcludes().contains( normalized ) )
622                 {
623                     filteredLines.add( normalized );
624                 }
625                 else
626                 {
627                     filtered = true;
628                     this.getExcludedProviders().add( normalized );
629                     this.getProject().log( Messages.getMessage( "providerExclusion", resource.toExternalForm(),
630                                                                 line.trim() ), Project.MSG_DEBUG );
631 
632                 }
633             }
634 
635             reader.close();
636             reader = null;
637             in = null;
638 
639             if ( filtered )
640             {
641                 final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
642                 this.temporaryResources.add( tmpResource );
643 
644                 out = new FileOutputStream( tmpResource );
645                 writer = new BufferedWriter( new OutputStreamWriter( out, "UTF-8" ) );
646 
647                 for ( final String line : filteredLines )
648                 {
649                     writer.write( line );
650                     writer.newLine();
651                 }
652 
653                 writer.close();
654                 writer = null;
655                 out = null;
656 
657                 filteredResource = tmpResource.toURI().toURL();
658             }
659 
660             return filteredResource;
661         }
662         finally
663         {
664             try
665             {
666                 if ( reader != null )
667                 {
668                     reader.close();
669                 }
670             }
671             catch ( final IOException e )
672             {
673                 this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
674             }
675             finally
676             {
677                 try
678                 {
679                     if ( in != null )
680                     {
681                         in.close();
682                     }
683                 }
684                 catch ( final IOException e )
685                 {
686                     this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
687                 }
688                 finally
689                 {
690                     try
691                     {
692                         if ( writer != null )
693                         {
694                             writer.close();
695                         }
696                     }
697                     catch ( final IOException e )
698                     {
699                         this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
700                     }
701                     finally
702                     {
703                         try
704                         {
705                             if ( out != null )
706                             {
707                                 out.close();
708                             }
709                         }
710                         catch ( final IOException e )
711                         {
712                             this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
713                         }
714                     }
715                 }
716             }
717         }
718     }
719 
720     private URL filterModlets( final URL resource ) throws ModelException, IOException, JAXBException
721     {
722         InputStream in = null;
723 
724         try
725         {
726             URL filteredResource = resource;
727             in = resource.openStream();
728 
729             final ModelContext modelContext = ModelContextFactory.newInstance().newModelContext();
730             final JAXBElement<?> e =
731                 (JAXBElement<?>) modelContext.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID ).unmarshal( in );
732 
733             in.close();
734             in = null;
735 
736             final Object o = e.getValue();
737             Modlets modlets = null;
738             boolean filtered = false;
739 
740             if ( o instanceof Modlets )
741             {
742                 modlets = (Modlets) o;
743             }
744             else if ( o instanceof Modlet )
745             {
746                 modlets = new Modlets();
747                 modlets.getModlet().add( (Modlet) o );
748             }
749 
750             if ( modlets != null )
751             {
752                 for ( final Iterator<Modlet> it = modlets.getModlet().iterator(); it.hasNext(); )
753                 {
754                     final Modlet m = it.next();
755 
756                     if ( this.getModletExcludes().contains( m.getName() ) )
757                     {
758                         it.remove();
759                         filtered = true;
760                         this.addExcludedModlet( m );
761                         this.getProject().log( Messages.getMessage( "modletExclusion", resource.toExternalForm(),
762                                                                     m.getName() ), Project.MSG_DEBUG );
763 
764                         continue;
765                     }
766 
767                     if ( this.filterModlet( m, resource.toExternalForm() ) )
768                     {
769                         filtered = true;
770                     }
771                 }
772 
773                 if ( filtered )
774                 {
775                     final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
776                     this.temporaryResources.add( tmpResource );
777                     modelContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID ).marshal(
778                         new ObjectFactory().createModlets( modlets ), tmpResource );
779 
780                     filteredResource = tmpResource.toURI().toURL();
781                 }
782             }
783 
784             return filteredResource;
785         }
786         finally
787         {
788             try
789             {
790                 if ( in != null )
791                 {
792                     in.close();
793                 }
794             }
795             catch ( final IOException e )
796             {
797                 this.project.log( Messages.getMessage( e ), e, Project.MSG_ERR );
798             }
799         }
800     }
801 
802     private boolean filterModlet( final Modlet modlet, final String resourceInfo )
803     {
804         boolean filteredSchemas = false;
805         boolean filteredServices = false;
806 
807         if ( modlet.getSchemas() != null )
808         {
809             final Schemas schemas = new Schemas();
810 
811             for ( final Schema s : modlet.getSchemas().getSchema() )
812             {
813                 if ( !this.getSchemaExcludes().contains( s.getPublicId() ) )
814                 {
815                     schemas.getSchema().add( s );
816                 }
817                 else
818                 {
819                     this.getProject().log( Messages.getMessage( "schemaExclusion", resourceInfo, s.getPublicId() ),
820                                            Project.MSG_DEBUG );
821 
822                     this.addExcludedSchema( s );
823                     filteredSchemas = true;
824                 }
825             }
826 
827             if ( filteredSchemas )
828             {
829                 modlet.setSchemas( schemas );
830             }
831         }
832 
833         if ( modlet.getServices() != null )
834         {
835             final Services services = new Services();
836 
837             for ( final Service s : modlet.getServices().getService() )
838             {
839                 if ( !this.getServiceExcludes().contains( s.getClazz() ) )
840                 {
841                     services.getService().add( s );
842                 }
843                 else
844                 {
845                     this.getProject().log( Messages.getMessage( "serviceExclusion", resourceInfo, s.getClazz() ),
846                                            Project.MSG_DEBUG );
847 
848                     this.addExcludedService( s );
849                     filteredServices = true;
850                 }
851             }
852 
853             if ( filteredServices )
854             {
855                 modlet.setServices( services );
856             }
857         }
858 
859         return filteredSchemas || filteredServices;
860     }
861 
862     private synchronized void addExcludedModlet( final Modlet modlet )
863     {
864         try
865         {
866             final Modlet m = this.getExcludedModlets().getModlet( modlet.getName() );
867 
868             if ( m != null )
869             {
870                 if ( m.getVersion() != null && modlet.getVersion() != null
871                          && VersionParser.compare( m.getVersion(), modlet.getVersion() ) < 0 )
872                 {
873                     this.getExcludedModlets().getModlet().remove( m );
874                     this.getExcludedModlets().getModlet().add( modlet );
875                 }
876             }
877             else
878             {
879                 this.getExcludedModlets().getModlet().add( modlet );
880             }
881         }
882         catch ( final ParseException e )
883         {
884             this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
885         }
886         catch ( final TokenMgrError e )
887         {
888             this.getProject().log( Messages.getMessage( e ), e, Project.MSG_WARN );
889         }
890     }
891 
892     private synchronized void addExcludedSchema( final Schema schema )
893     {
894         if ( this.getExcludedSchemas().getSchemaBySystemId( schema.getSystemId() ) == null )
895         {
896             this.getExcludedSchemas().getSchema().add( schema );
897         }
898     }
899 
900     private synchronized void addExcludedService( final Service service )
901     {
902         for ( int i = 0, s0 = this.getExcludedServices().getService().size(); i < s0; i++ )
903         {
904             final Service s = this.getExcludedServices().getService().get( i );
905 
906             if ( s.getIdentifier().equals( service.getIdentifier() ) && s.getClazz().equals( service.getClazz() ) )
907             {
908                 return;
909             }
910         }
911 
912         this.getExcludedServices().getService().add( service );
913     }
914 
915     private static Set<String> readDefaultExcludes( final String location ) throws IOException
916     {
917         InputStream in = null;
918         BufferedReader reader = null;
919         final Set<String> defaultExcludes = new HashSet<String>();
920 
921         try
922         {
923             in = ProjectClassLoader.class.getResourceAsStream( location );
924             assert in != null : "Expected resource '" + location + "' not found.";
925             reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
926 
927             for ( String line = reader.readLine(); line != null; line = reader.readLine() )
928             {
929                 final String normalized = line.trim();
930 
931                 if ( normalized.length() > 0 && !normalized.contains( "#" ) )
932                 {
933                     defaultExcludes.add( line.trim() );
934                 }
935             }
936 
937             reader.close();
938             reader = null;
939             in = null;
940 
941             return Collections.unmodifiableSet( defaultExcludes );
942         }
943         finally
944         {
945             try
946             {
947                 if ( reader != null )
948                 {
949                     reader.close();
950                 }
951             }
952             catch ( final IOException e )
953             {
954                 // Suppressed.
955             }
956             finally
957             {
958                 try
959                 {
960                     if ( in != null )
961                     {
962                         in.close();
963                     }
964                 }
965                 catch ( final IOException e )
966                 {
967                     // Suppressed.
968                 }
969             }
970         }
971     }
972 
973 }