001/*
002 * Copyright (C) 2009 Christian Schulte <cs@schulte.it>
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 *   o Redistributions of source code must retain the above copyright
010 *     notice, this list of conditions and the following disclaimer.
011 *
012 *   o Redistributions in binary form must reproduce the above copyright
013 *     notice, this list of conditions and the following disclaimer in
014 *     the documentation and/or other materials provided with the
015 *     distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 * $JOMC: AbstractModletCommand.java 5303 2016-08-30 02:31:20Z schulte $
029 *
030 */
031package org.jomc.cli.commands;
032
033import java.io.BufferedReader;
034import java.io.BufferedWriter;
035import java.io.Closeable;
036import java.io.File;
037import java.io.FileOutputStream;
038import java.io.FileReader;
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.InputStreamReader;
042import java.io.OutputStream;
043import java.io.OutputStreamWriter;
044import java.io.StringWriter;
045import java.net.MalformedURLException;
046import java.net.URI;
047import java.net.URISyntaxException;
048import java.net.URL;
049import java.net.URLClassLoader;
050import java.util.Collections;
051import java.util.Enumeration;
052import java.util.HashSet;
053import java.util.Iterator;
054import java.util.LinkedList;
055import java.util.List;
056import java.util.Map;
057import java.util.Set;
058import java.util.logging.Level;
059import javax.xml.bind.JAXBElement;
060import javax.xml.bind.JAXBException;
061import javax.xml.bind.Marshaller;
062import javax.xml.bind.PropertyException;
063import javax.xml.transform.ErrorListener;
064import javax.xml.transform.Source;
065import javax.xml.transform.Transformer;
066import javax.xml.transform.TransformerConfigurationException;
067import javax.xml.transform.TransformerException;
068import javax.xml.transform.TransformerFactory;
069import org.apache.commons.cli.CommandLine;
070import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
071import org.jomc.model.ModelObject;
072import org.jomc.modlet.DefaultModelContext;
073import org.jomc.modlet.DefaultModletProvider;
074import org.jomc.modlet.ModelContext;
075import org.jomc.modlet.ModelContextFactory;
076import org.jomc.modlet.ModelException;
077import org.jomc.modlet.ModelValidationReport;
078import org.jomc.modlet.Modlet;
079import org.jomc.modlet.ModletObject;
080import org.jomc.modlet.ModletProcessor;
081import org.jomc.modlet.ModletProvider;
082import org.jomc.modlet.ModletValidator;
083import org.jomc.modlet.Modlets;
084import org.jomc.modlet.ObjectFactory;
085import org.jomc.modlet.Schema;
086import org.jomc.modlet.Schemas;
087import org.jomc.modlet.Service;
088import org.jomc.modlet.ServiceFactory;
089import org.jomc.modlet.Services;
090
091/**
092 * {@code ModelContext} based command implementation.
093 *
094 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
095 */
096public abstract class AbstractModletCommand extends AbstractCommand
097{
098
099    /**
100     * Constant to prefix relative resource names with.
101     */
102    private static final String ABSOLUTE_RESOURCE_NAME_PREFIX =
103        "/" + AbstractModletCommand.class.getPackage().getName().replace( '.', '/' ) + "/";
104
105    /**
106     * Creates a new {@code AbstractModletCommand} instance.
107     */
108    public AbstractModletCommand()
109    {
110        super();
111    }
112
113    @Override
114    public org.apache.commons.cli.Options getOptions()
115    {
116        final org.apache.commons.cli.Options options = super.getOptions();
117        options.addOption( Options.CLASSPATH_OPTION );
118        options.addOption( Options.DOCUMENTS_OPTION );
119        options.addOption( Options.MODEL_CONTEXT_FACTORY_CLASSNAME_OPTION );
120        options.addOption( Options.MODEL_OPTION );
121        options.addOption( Options.MODLET_SCHEMA_SYSTEM_ID_OPTION );
122        options.addOption( Options.MODLET_LOCATION_OPTION );
123        options.addOption( Options.PROVIDER_LOCATION_OPTION );
124        options.addOption( Options.PLATFORM_PROVIDER_LOCATION_OPTION );
125        options.addOption( Options.NO_MODLET_RESOURCE_VALIDATION_OPTION );
126        return options;
127    }
128
129    /**
130     * Creates a new {@code Transformer} from a given {@code Source}.
131     *
132     * @param source The source to initialize the transformer with.
133     *
134     * @return A {@code Transformer} backed by {@code source}.
135     *
136     * @throws NullPointerException if {@code source} is {@code null}.
137     * @throws CommandExecutionException if creating a transformer fails.
138     */
139    protected Transformer createTransformer( final Source source ) throws CommandExecutionException
140    {
141        if ( source == null )
142        {
143            throw new NullPointerException( "source" );
144        }
145
146        final ErrorListener errorListener = new ErrorListener()
147        {
148
149            public void warning( final TransformerException exception ) throws TransformerException
150            {
151                log( Level.WARNING, null, exception );
152            }
153
154            public void error( final TransformerException exception ) throws TransformerException
155            {
156                throw exception;
157            }
158
159            public void fatalError( final TransformerException exception ) throws TransformerException
160            {
161                throw exception;
162            }
163
164        };
165
166        try
167        {
168            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
169            transformerFactory.setErrorListener( errorListener );
170            final Transformer transformer = transformerFactory.newTransformer( source );
171            transformer.setErrorListener( errorListener );
172
173            for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
174            {
175                transformer.setParameter( e.getKey().toString(), e.getValue() );
176            }
177
178            return transformer;
179        }
180        catch ( final TransformerConfigurationException e )
181        {
182            throw new CommandExecutionException( Messages.getMessage( e ), e );
183        }
184    }
185
186    /**
187     * Creates a new {@code ModelContext} for a given {@code CommandLine} and {@code ClassLoader}.
188     *
189     * @param commandLine The {@code CommandLine} to create a new {@code ModelContext} with.
190     * @param classLoader The {@code ClassLoader} to create a new {@code ModelContext} with.
191     *
192     * @return A new {@code ModelContext} for {@code classLoader} setup using {@code commandLine}.
193     *
194     * @throws NullPointerException if {@code commandLine} is {@code null}.
195     * @throws CommandExecutionException if creating an new {@code ModelContext} fails.
196     */
197    protected ModelContext createModelContext( final CommandLine commandLine, final ClassLoader classLoader )
198        throws CommandExecutionException
199    {
200        if ( commandLine == null )
201        {
202            throw new NullPointerException( "commandLine" );
203        }
204
205        final ModelContextFactory modelContextFactory =
206            commandLine.hasOption( Options.MODEL_CONTEXT_FACTORY_CLASSNAME_OPTION.getOpt() )
207                ? ModelContextFactory.newInstance( commandLine.getOptionValue(
208                Options.MODEL_CONTEXT_FACTORY_CLASSNAME_OPTION.getOpt() ) )
209                : ModelContextFactory.newInstance();
210
211        final ModelContext modelContext = modelContextFactory.newModelContext( classLoader );
212
213        modelContext.setExecutorService( this.getExecutorService( commandLine ) );
214
215        if ( commandLine.hasOption( Options.MODLET_SCHEMA_SYSTEM_ID_OPTION.getOpt() ) )
216        {
217            modelContext.setModletSchemaSystemId(
218                commandLine.getOptionValue( Options.MODLET_SCHEMA_SYSTEM_ID_OPTION.getOpt() ) );
219
220        }
221
222        modelContext.setLogLevel( this.getLogLevel() );
223        modelContext.getListeners().add( new ModelContext.Listener()
224        {
225
226            @Override
227            public void onLog( final Level level, final String message, final Throwable t )
228            {
229                super.onLog( level, message, t );
230                log( level, message, t );
231            }
232
233        } );
234
235        if ( commandLine.hasOption( Options.PROVIDER_LOCATION_OPTION.getOpt() ) )
236        {
237            modelContext.setAttribute( DefaultModelContext.PROVIDER_LOCATION_ATTRIBUTE_NAME,
238                                       commandLine.getOptionValue( Options.PROVIDER_LOCATION_OPTION.getOpt() ) );
239
240        }
241
242        if ( commandLine.hasOption( Options.PLATFORM_PROVIDER_LOCATION_OPTION.getOpt() ) )
243        {
244            modelContext.setAttribute(
245                DefaultModelContext.PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME,
246                commandLine.getOptionValue( Options.PLATFORM_PROVIDER_LOCATION_OPTION.getOpt() ) );
247
248        }
249
250        if ( commandLine.hasOption( Options.MODLET_LOCATION_OPTION.getOpt() ) )
251        {
252            modelContext.setAttribute( DefaultModletProvider.MODLET_LOCATION_ATTRIBUTE_NAME,
253                                       commandLine.getOptionValue( Options.MODLET_LOCATION_OPTION.getOpt() ) );
254
255        }
256
257        modelContext.setAttribute( DefaultModletProvider.VALIDATING_ATTRIBUTE_NAME,
258                                   !commandLine.hasOption( Options.NO_MODLET_RESOURCE_VALIDATION_OPTION.getOpt() ) );
259
260        return modelContext;
261    }
262
263    /**
264     * Gets the identifier of the model to process.
265     *
266     * @param commandLine The command line to get the identifier of the model to process from.
267     *
268     * @return The identifier of the model to process.
269     *
270     * @throws NullPointerException if {@code commandLine} is {@code null}.
271     */
272    protected String getModel( final CommandLine commandLine )
273    {
274        if ( commandLine == null )
275        {
276            throw new NullPointerException( "commandLine" );
277        }
278
279        return commandLine.hasOption( Options.MODEL_OPTION.getOpt() )
280                   ? commandLine.getOptionValue( Options.MODEL_OPTION.getOpt() )
281                   : ModelObject.MODEL_PUBLIC_ID;
282
283    }
284
285    /**
286     * Logs a validation report.
287     *
288     * @param validationReport The report to log.
289     * @param marshaller The marshaller to use for logging the report.
290     *
291     * @throws CommandExecutionException if logging a report detail element fails.
292     */
293    protected void log( final ModelValidationReport validationReport, final Marshaller marshaller )
294        throws CommandExecutionException
295    {
296        Object jaxbFormattedOutput;
297        try
298        {
299            jaxbFormattedOutput = marshaller.getProperty( Marshaller.JAXB_FORMATTED_OUTPUT );
300            marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
301        }
302        catch ( final PropertyException e )
303        {
304            this.log( Level.INFO, Messages.getMessage( e ), e );
305            jaxbFormattedOutput = null;
306        }
307
308        try
309        {
310            for ( final ModelValidationReport.Detail d : validationReport.getDetails() )
311            {
312                if ( this.isLoggable( d.getLevel() ) )
313                {
314                    this.log( d.getLevel(), "o " + d.getMessage(), null );
315
316                    if ( d.getElement() != null && this.getLogLevel().intValue() < Level.INFO.intValue() )
317                    {
318                        final StringWriter stringWriter = new StringWriter();
319                        marshaller.marshal( d.getElement(), stringWriter );
320                        this.log( d.getLevel(), stringWriter.toString(), null );
321                    }
322                }
323            }
324        }
325        catch ( final JAXBException e )
326        {
327            String message = Messages.getMessage( e );
328            if ( message == null )
329            {
330                message = Messages.getMessage( e.getLinkedException() );
331            }
332
333            throw new CommandExecutionException( message, e );
334        }
335        finally
336        {
337            try
338            {
339                marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, jaxbFormattedOutput );
340            }
341            catch ( final PropertyException e )
342            {
343                this.log( Level.INFO, Messages.getMessage( e ), e );
344            }
345        }
346    }
347
348    /**
349     * Gets the document files specified by a given command line.
350     *
351     * @param commandLine The command line specifying the document files to get.
352     *
353     * @return The document files specified by {@code commandLine}.
354     *
355     * @throws CommandExecutionException if getting the document files fails.
356     */
357    protected Set<File> getDocumentFiles( final CommandLine commandLine ) throws CommandExecutionException
358    {
359        try
360        {
361            final Set<File> files = new HashSet<File>( 128 );
362
363            if ( commandLine.hasOption( Options.DOCUMENTS_OPTION.getOpt() ) )
364            {
365                final String[] elements = commandLine.getOptionValues( Options.DOCUMENTS_OPTION.getOpt() );
366
367                if ( elements != null )
368                {
369                    for ( final String e : elements )
370                    {
371                        if ( e.startsWith( "@" ) )
372                        {
373                            final File file = new File( e.substring( 1 ) );
374                            BufferedReader reader = null;
375
376                            try
377                            {
378                                reader = new BufferedReader( new FileReader( file ) );
379
380                                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
381                                {
382                                    line = line.trim();
383
384                                    if ( !line.startsWith( "#" ) )
385                                    {
386                                        final File f = new File( line );
387
388                                        if ( f.exists() )
389                                        {
390                                            if ( this.isLoggable( Level.FINER ) )
391                                            {
392                                                this.log( Level.FINER,
393                                                          Messages.getMessage( "documentFileInfo",
394                                                                               f.getAbsolutePath() ),
395                                                          null );
396
397                                            }
398
399                                            files.add( f );
400                                        }
401                                        else if ( this.isLoggable( Level.WARNING ) )
402                                        {
403                                            this.log( Level.WARNING,
404                                                      Messages.getMessage( "documentFileNotFoundWarning",
405                                                                           f.getAbsolutePath() ),
406                                                      null );
407
408                                        }
409                                    }
410                                }
411
412                                reader.close();
413                                reader = null;
414                            }
415                            finally
416                            {
417                                try
418                                {
419                                    if ( reader != null )
420                                    {
421                                        reader.close();
422                                    }
423                                }
424                                catch ( final IOException ex )
425                                {
426                                    this.log( Level.SEVERE, Messages.getMessage( ex ), ex );
427                                }
428                            }
429                        }
430                        else
431                        {
432                            final File file = new File( e );
433
434                            if ( file.exists() )
435                            {
436                                if ( this.isLoggable( Level.FINER ) )
437                                {
438                                    this.log( Level.FINER,
439                                              Messages.getMessage( "documentFileInfo", file.getAbsolutePath() ),
440                                              null );
441
442                                }
443
444                                files.add( file );
445                            }
446                            else if ( this.isLoggable( Level.WARNING ) )
447                            {
448                                this.log( Level.WARNING,
449                                          Messages.getMessage( "documentFileNotFoundWarning", file.getAbsolutePath() ),
450                                          null );
451
452                            }
453                        }
454                    }
455                }
456            }
457
458            return files;
459        }
460        catch ( final IOException e )
461        {
462            throw new CommandExecutionException( Messages.getMessage( e ), e );
463        }
464    }
465
466    /**
467     * Class loader backed by a command line.
468     *
469     * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
470     * @version $JOMC: AbstractModletCommand.java 5303 2016-08-30 02:31:20Z schulte $
471     */
472    public class CommandLineClassLoader extends URLClassLoader
473    {
474
475        /**
476         * {@code Modlets} excluded by the instance.
477         */
478        private final Modlets excludedModlets = new Modlets();
479
480        /**
481         * Set of provider resource locations to filter.
482         */
483        private final Set<String> providerResourceLocations = new HashSet<String>( 128 );
484
485        /**
486         * Set of modlet resource locations to filter.
487         */
488        private final Set<String> modletResourceLocations = new HashSet<String>( 128 );
489
490        /**
491         * Set of temporary resources.
492         */
493        private final Set<File> temporaryResources = new HashSet<File>( 128 );
494
495        /**
496         * Creates a new {@code CommandLineClassLoader} taking a command line backing the class loader.
497         *
498         * @param commandLine The command line backing the class loader.
499         *
500         * @throws NullPointerException if {@code commandLine} is {@code null}.
501         * @throws CommandExecutionException if processing {@code commandLine} fails.
502         */
503        public CommandLineClassLoader( final CommandLine commandLine ) throws CommandExecutionException
504        {
505            super( new URL[ 0 ] );
506
507            try
508            {
509                if ( commandLine.hasOption( Options.CLASSPATH_OPTION.getOpt() ) )
510                {
511                    final Set<URI> uris = new HashSet<URI>( 128 );
512                    final String[] elements = commandLine.getOptionValues( Options.CLASSPATH_OPTION.getOpt() );
513
514                    if ( elements != null )
515                    {
516                        for ( final String e : elements )
517                        {
518                            if ( e.startsWith( "@" ) )
519                            {
520                                final File file = new File( e.substring( 1 ) );
521                                BufferedReader reader = null;
522
523                                try
524                                {
525                                    reader = new BufferedReader( new FileReader( file ) );
526
527                                    for ( String line = reader.readLine(); line != null; line = reader.readLine() )
528                                    {
529                                        line = line.trim();
530
531                                        if ( !line.startsWith( "#" ) )
532                                        {
533                                            final File f = new File( line );
534
535                                            if ( f.exists() )
536                                            {
537                                                uris.add( f.toURI() );
538                                            }
539                                            else if ( isLoggable( Level.WARNING ) )
540                                            {
541                                                log( Level.WARNING,
542                                                     Messages.getMessage( "classpathElementNotFoundWarning",
543                                                                          f.getAbsolutePath() ),
544                                                     null );
545
546                                            }
547                                        }
548                                    }
549
550                                    reader.close();
551                                    reader = null;
552                                }
553                                finally
554                                {
555                                    try
556                                    {
557                                        if ( reader != null )
558                                        {
559                                            reader.close();
560                                        }
561                                    }
562                                    catch ( final IOException ex )
563                                    {
564                                        log( Level.SEVERE, Messages.getMessage( ex ), ex );
565                                    }
566                                }
567                            }
568                            else
569                            {
570                                final File file = new File( e );
571
572                                if ( file.exists() )
573                                {
574                                    uris.add( file.toURI() );
575                                }
576                                else if ( isLoggable( Level.WARNING ) )
577                                {
578                                    log( Level.WARNING,
579                                         Messages.getMessage( "classpathElementNotFoundWarning",
580                                                              file.getAbsolutePath() ),
581                                         null );
582
583                                }
584                            }
585                        }
586                    }
587
588                    for ( final URI uri : uris )
589                    {
590                        if ( isLoggable( Level.FINEST ) )
591                        {
592                            log( Level.FINEST,
593                                 Messages.getMessage( "classpathElementInfo", uri.toASCIIString() ),
594                                 null );
595
596                        }
597
598                        this.addURL( uri.toURL() );
599                    }
600
601                    // Assumes the default modlet location matches the location of resources of the applications'
602                    // dependencies.
603                    this.modletResourceLocations.add( DefaultModletProvider.getDefaultModletLocation() );
604
605                    // Assumes the default provider location matches the location of resources of the applications'
606                    // dependencies.
607                    final String providerLocationPrefix = DefaultModelContext.getDefaultProviderLocation() + "/";
608                    this.providerResourceLocations.add( providerLocationPrefix + ModletProcessor.class.getName() );
609                    this.providerResourceLocations.add( providerLocationPrefix + ModletProvider.class.getName() );
610                    this.providerResourceLocations.add( providerLocationPrefix + ModletValidator.class.getName() );
611                    this.providerResourceLocations.add( providerLocationPrefix + ServiceFactory.class.getName() );
612                }
613            }
614            catch ( final IOException e )
615            {
616                throw new CommandExecutionException( Messages.getMessage( e ), e );
617            }
618        }
619
620        /**
621         * Gets the {@code Modlets} excluded by the instance.
622         *
623         * @return The {@code Modlets} excluded by the instance.
624         */
625        public Modlets getExcludedModlets()
626        {
627            return this.excludedModlets;
628        }
629
630        /**
631         * Finds a resource with a given name.
632         *
633         * @param name The name of the resource to search.
634         *
635         * @return An {@code URL} object for reading the resource or {@code null}, if no resource matching {@code name} is
636         * found.
637         */
638        @Override
639        public URL findResource( final String name ) //JDK: As of JDK 23 throws IOException
640        {
641            try
642            {
643                URL resource = super.findResource( name );
644
645                if ( resource != null )
646                {
647                    if ( this.providerResourceLocations.contains( name ) )
648                    {
649                        resource = this.filterProviders( resource );
650                    }
651                    else if ( this.modletResourceLocations.contains( name ) )
652                    {
653                        resource = this.filterModlets( resource );
654                    }
655                }
656
657                return resource;
658            }
659            catch ( final IOException e )
660            {
661                log( Level.SEVERE, Messages.getMessage( e ), e );
662                return null;
663            }
664            catch ( final JAXBException e )
665            {
666                log( Level.SEVERE, Messages.getMessage( e ), e );
667                return null;
668            }
669            catch ( final ModelException e )
670            {
671                log( Level.SEVERE, Messages.getMessage( e ), e );
672                return null;
673            }
674        }
675
676        /**
677         * Finds all resources matching a given name.
678         *
679         * @param name The name of the resources to search.
680         *
681         * @return An enumeration of {@code URL} objects of resources matching name.
682         *
683         * @throws IOException if getting resources fails.
684         */
685        @Override
686        public Enumeration<URL> findResources( final String name ) throws IOException
687        {
688            try
689            {
690                Enumeration<URL> resources = super.findResources( name );
691
692                if ( this.providerResourceLocations.contains( name )
693                         || this.modletResourceLocations.contains( name ) )
694                {
695                    final List<URI> filtered = new LinkedList<URI>();
696
697                    while ( resources.hasMoreElements() )
698                    {
699                        final URL resource = resources.nextElement();
700
701                        if ( this.providerResourceLocations.contains( name ) )
702                        {
703                            filtered.add( this.filterProviders( resource ).toURI() );
704                        }
705                        else if ( this.modletResourceLocations.contains( name ) )
706                        {
707                            filtered.add( this.filterModlets( resource ).toURI() );
708                        }
709                    }
710
711                    final Iterator<URI> it = filtered.iterator();
712
713                    resources = new Enumeration<URL>()
714                    {
715
716                        public boolean hasMoreElements()
717                        {
718                            return it.hasNext();
719                        }
720
721                        public URL nextElement()
722                        {
723                            try
724                            {
725                                return it.next().toURL();
726                            }
727                            catch ( final MalformedURLException e )
728                            {
729                                throw new AssertionError( e );
730                            }
731                        }
732
733                    };
734                }
735
736                return resources;
737            }
738            catch ( final URISyntaxException e )
739            {
740                // JDK: As of JDK 6, new IOException( message, e );
741                throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
742            }
743            catch ( final JAXBException e )
744            {
745                String message = Messages.getMessage( e );
746                if ( message == null && e.getLinkedException() != null )
747                {
748                    message = Messages.getMessage( e.getLinkedException() );
749                }
750
751                // JDK: As of JDK 6, new IOException( message, e );
752                throw (IOException) new IOException( message ).initCause( e );
753            }
754            catch ( final ModelException e )
755            {
756                // JDK: As of JDK 6, new IOException( message, e );
757                throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
758            }
759        }
760
761        /**
762         * Closes the class loader.
763         *
764         * @throws IOException if closing the class loader fails.
765         */
766        @Override
767        @IgnoreJRERequirement
768        public void close() throws IOException
769        {
770            for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
771            {
772                final File temporaryResource = it.next();
773
774                if ( temporaryResource.exists() && temporaryResource.delete() )
775                {
776                    it.remove();
777                }
778            }
779
780            if ( Closeable.class.isAssignableFrom( CommandLineClassLoader.class ) )
781            {
782                super.close();
783            }
784        }
785
786        /**
787         * Removes temporary resources.
788         *
789         * @throws Throwable if finalization fails.
790         */
791        @Override
792        protected void finalize() throws Throwable
793        {
794            for ( final Iterator<File> it = this.temporaryResources.iterator(); it.hasNext(); )
795            {
796                final File temporaryResource = it.next();
797
798                if ( temporaryResource.exists() && !temporaryResource.delete() )
799                {
800                    temporaryResource.deleteOnExit();
801                }
802
803                it.remove();
804            }
805
806            super.finalize();
807        }
808
809        private URL filterProviders( final URL resource ) throws IOException
810        {
811            InputStream in = null;
812            BufferedReader reader = null;
813            OutputStream out = null;
814            BufferedWriter writer = null;
815            final Set<String> providerExcludes = this.getProviderExcludes();
816            final List<String> lines = new LinkedList<String>();
817
818            try
819            {
820                URL filteredResource = resource;
821                boolean filtered = false;
822                in = resource.openStream();
823                reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
824
825                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
826                {
827                    if ( !providerExcludes.contains( line.trim() ) )
828                    {
829                        lines.add( line );
830                    }
831                    else
832                    {
833                        filtered = true;
834                        log( Level.FINE,
835                             Messages.getMessage( "providerExclusionInfo", resource.toExternalForm(), line ),
836                             null );
837
838                    }
839                }
840
841                reader.close();
842                reader = null;
843                in = null;
844
845                if ( filtered )
846                {
847                    final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
848                    this.temporaryResources.add( tmpResource );
849
850                    out = new FileOutputStream( tmpResource );
851                    writer = new BufferedWriter( new OutputStreamWriter( out, "UTF-8" ) );
852
853                    for ( final String line : lines )
854                    {
855                        writer.write( line );
856                        writer.newLine();
857                    }
858
859                    writer.close();
860                    writer = null;
861                    out = null;
862
863                    filteredResource = tmpResource.toURI().toURL();
864                }
865
866                return filteredResource;
867            }
868            finally
869            {
870                try
871                {
872                    if ( reader != null )
873                    {
874                        reader.close();
875                    }
876                }
877                catch ( final IOException e )
878                {
879                    log( Level.SEVERE, Messages.getMessage( e ), e );
880                }
881                finally
882                {
883                    try
884                    {
885                        if ( in != null )
886                        {
887                            in.close();
888                        }
889                    }
890                    catch ( final IOException e )
891                    {
892                        log( Level.SEVERE, Messages.getMessage( e ), e );
893                    }
894                    finally
895                    {
896                        try
897                        {
898                            if ( writer != null )
899                            {
900                                writer.close();
901                            }
902                        }
903                        catch ( final IOException e )
904                        {
905                            log( Level.SEVERE, Messages.getMessage( e ), e );
906                        }
907                        finally
908                        {
909                            try
910                            {
911                                if ( out != null )
912                                {
913                                    out.close();
914                                }
915                            }
916                            catch ( final IOException e )
917                            {
918                                log( Level.SEVERE, Messages.getMessage( e ), e );
919                            }
920                        }
921                    }
922                }
923            }
924        }
925
926        private URL filterModlets( final URL resource ) throws ModelException, IOException, JAXBException
927        {
928            URL filteredResource = resource;
929            final Set<String> excludedModletNames = this.getModletExcludes();
930            final ModelContext modelContext = ModelContextFactory.newInstance().newModelContext();
931            Object o = modelContext.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID ).unmarshal( resource );
932            if ( o instanceof JAXBElement<?> )
933            {
934                o = ( (JAXBElement<?>) o ).getValue();
935            }
936
937            Modlets modlets = null;
938            boolean filtered = false;
939
940            if ( o instanceof Modlets )
941            {
942                modlets = (Modlets) o;
943            }
944            else if ( o instanceof Modlet )
945            {
946                modlets = new Modlets();
947                modlets.getModlet().add( (Modlet) o );
948            }
949
950            if ( modlets != null )
951            {
952                for ( final Iterator<Modlet> it = modlets.getModlet().iterator(); it.hasNext(); )
953                {
954                    final Modlet m = it.next();
955
956                    if ( excludedModletNames.contains( m.getName() ) )
957                    {
958                        it.remove();
959                        filtered = true;
960                        synchronized ( this )
961                        {
962                            this.getExcludedModlets().getModlet().add( m );
963                        }
964                        log( Level.FINE,
965                             Messages.getMessage( "modletExclusionInfo", resource.toExternalForm(), m.getName() ),
966                             null );
967
968                        continue;
969                    }
970
971                    if ( this.filterModlet( m, resource.toExternalForm() ) )
972                    {
973                        filtered = true;
974                    }
975                }
976
977                if ( filtered )
978                {
979                    final File tmpResource = File.createTempFile( this.getClass().getName(), ".rsrc" );
980                    this.temporaryResources.add( tmpResource );
981                    modelContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID ).
982                        marshal( new ObjectFactory().createModlets( modlets ), tmpResource );
983
984                    filteredResource = tmpResource.toURI().toURL();
985                }
986            }
987
988            return filteredResource;
989        }
990
991        private boolean filterModlet( final Modlet modlet, final String resourceInfo ) throws IOException
992        {
993            boolean filteredSchemas = false;
994            boolean filteredServices = false;
995            final Set<String> excludedSchemas = this.getSchemaExcludes();
996            final Set<String> excludedServices = this.getServiceExcludes();
997
998            if ( modlet.getSchemas() != null )
999            {
1000                final Schemas schemas = new Schemas();
1001
1002                for ( final Schema s : modlet.getSchemas().getSchema() )
1003                {
1004                    if ( !excludedSchemas.contains( s.getPublicId() ) )
1005                    {
1006                        schemas.getSchema().add( s );
1007                    }
1008                    else
1009                    {
1010                        log( Level.FINE,
1011                             Messages.getMessage( "schemaExclusionInfo", resourceInfo, s.getContextId() ),
1012                             null );
1013
1014                        filteredSchemas = true;
1015                    }
1016                }
1017
1018                if ( filteredSchemas )
1019                {
1020                    modlet.setSchemas( schemas );
1021                }
1022            }
1023
1024            if ( modlet.getServices() != null )
1025            {
1026                final Services services = new Services();
1027
1028                for ( final Service s : modlet.getServices().getService() )
1029                {
1030                    if ( !excludedServices.contains( s.getClazz() ) )
1031                    {
1032                        services.getService().add( s );
1033                    }
1034                    else
1035                    {
1036                        log( Level.FINE,
1037                             Messages.getMessage( "serviceExclusionInfo", resourceInfo, s.getClazz() ),
1038                             null );
1039
1040                        filteredServices = true;
1041                    }
1042                }
1043
1044                if ( filteredServices )
1045                {
1046                    modlet.setServices( services );
1047                }
1048            }
1049
1050            return filteredSchemas || filteredServices;
1051        }
1052
1053        /**
1054         * Gets a set of modlet names to filter.
1055         *
1056         * @return An unmodifiable set of modlet names to filter.
1057         *
1058         * @throws IOException if reading configuration resources fails.
1059         */
1060        private Set<String> getModletExcludes() throws IOException
1061        {
1062            return this.readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultModletExcludes" );
1063        }
1064
1065        /**
1066         * Gets a set of provider names to filter.
1067         *
1068         * @return An unmodifiable set of provider names to filter.
1069         *
1070         * @throws IOException if reading configuration resources fails.
1071         */
1072        private Set<String> getProviderExcludes() throws IOException
1073        {
1074            return this.readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultProviderExcludes" );
1075        }
1076
1077        /**
1078         * Gets a set of service class names to filter.
1079         *
1080         * @return An unmodifiable set of service class names to filter.
1081         *
1082         * @throws IOException if reading configuration resources fails.
1083         */
1084        private Set<String> getServiceExcludes() throws IOException
1085        {
1086            return this.readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultServiceExcludes" );
1087        }
1088
1089        /**
1090         * Gets a set of schema public identifiers excluded by default.
1091         *
1092         * @return An unmodifiable set of schema public identifiers excluded by default.
1093         *
1094         * @throws IOException if reading configuration resources fails.
1095         */
1096        private Set<String> getSchemaExcludes() throws IOException
1097        {
1098            return this.readDefaultExcludes( ABSOLUTE_RESOURCE_NAME_PREFIX + "DefaultSchemaExcludes" );
1099        }
1100
1101        private Set<String> readDefaultExcludes( final String location ) throws IOException
1102        {
1103            InputStream in = null;
1104            BufferedReader reader = null;
1105            final Set<String> defaultExcludes = new HashSet<String>();
1106
1107            try
1108            {
1109                in = CommandLineClassLoader.class.getResourceAsStream( location );
1110                assert in != null : "Expected resource '" + location + "' not found.";
1111                reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
1112
1113                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
1114                {
1115                    final String normalized = line.trim();
1116
1117                    if ( normalized.length() > 0 && !normalized.contains( "#" ) )
1118                    {
1119                        defaultExcludes.add( normalized );
1120                    }
1121                }
1122
1123                reader.close();
1124                reader = null;
1125                in = null;
1126
1127                return Collections.unmodifiableSet( defaultExcludes );
1128            }
1129            finally
1130            {
1131                try
1132                {
1133                    if ( reader != null )
1134                    {
1135                        reader.close();
1136                    }
1137                }
1138                catch ( final IOException e )
1139                {
1140                    // Suppressed.
1141                }
1142                finally
1143                {
1144                    try
1145                    {
1146                        if ( in != null )
1147                        {
1148                            in.close();
1149                        }
1150                    }
1151                    catch ( final IOException e )
1152                    {
1153                        // Suppressed.
1154                    }
1155                }
1156            }
1157        }
1158
1159    }
1160
1161}