View Javadoc
1   package org.apache.maven.doxia.docrenderer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.LinkedHashMap;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  
38  import org.apache.maven.doxia.Doxia;
39  import org.apache.maven.doxia.document.DocumentModel;
40  import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader;
41  import org.apache.maven.doxia.sink.Sink;
42  import org.apache.maven.doxia.parser.ParseException;
43  import org.apache.maven.doxia.parser.Parser;
44  import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
45  import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
46  import org.apache.maven.doxia.parser.module.ParserModule;
47  import org.apache.maven.doxia.parser.module.ParserModuleManager;
48  import org.apache.maven.doxia.util.XmlValidator;
49  
50  import org.apache.velocity.VelocityContext;
51  import org.apache.velocity.context.Context;
52  
53  import org.codehaus.plexus.component.annotations.Requirement;
54  import org.codehaus.plexus.logging.AbstractLogEnabled;
55  
56  import org.codehaus.plexus.util.DirectoryScanner;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.IOUtil;
59  import org.codehaus.plexus.util.ReaderFactory;
60  import org.codehaus.plexus.util.xml.XmlStreamReader;
61  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62  import org.codehaus.plexus.velocity.SiteResourceLoader;
63  import org.codehaus.plexus.velocity.VelocityComponent;
64  
65  /**
66   * Abstract <code>document</code> renderer.
67   *
68   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
69   * @author ltheussl
70   * @version $Id: AbstractDocumentRenderer.java 1726406 2016-01-23 15:06:45Z hboutemy $
71   * @since 1.1
72   */
73  public abstract class AbstractDocumentRenderer
74      extends AbstractLogEnabled
75      implements DocumentRenderer
76  {
77      @Requirement
78      protected ParserModuleManager parserModuleManager;
79  
80      @Requirement
81      protected Doxia doxia;
82  
83      @Requirement
84      private VelocityComponent velocity;
85  
86      /**
87       * The common base directory of source files.
88       */
89      private String baseDir;
90  
91        //--------------------------------------------
92       //
93      //--------------------------------------------
94  
95      /**
96       * Render an aggregate document from the files found in a Map.
97       *
98       * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
99       *      source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
100      * @param outputDirectory the output directory where the aggregate document should be generated.
101      * @param documentModel the document model, containing all the metadata, etc.
102      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
103      * @throws java.io.IOException if any
104      * @deprecated since 1.1.2, use {@link #render(Map, File, DocumentModel, DocumentRendererContext)}
105      */
106     public abstract void render( Map<String, ParserModule> filesToProcess, File outputDirectory,
107                                  DocumentModel documentModel )
108         throws DocumentRendererException, IOException;
109 
110       //--------------------------------------------
111      //
112     //--------------------------------------------
113 
114     /** {@inheritDoc} */
115     public void render( Collection<String> files, File outputDirectory, DocumentModel documentModel )
116         throws DocumentRendererException, IOException
117     {
118         render( getFilesToProcess( files ), outputDirectory, documentModel, null );
119     }
120 
121     /** {@inheritDoc} */
122     public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel )
123         throws DocumentRendererException, IOException
124     {
125         render( baseDirectory, outputDirectory, documentModel, null );
126     }
127 
128     /**
129      * Render an aggregate document from the files found in a Map.
130      *
131      * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
132      *      source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
133      * @param outputDirectory the output directory where the aggregate document should be generated.
134      * @param documentModel the document model, containing all the metadata, etc.
135      * @param context the rendering context when processing files.
136      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
137      * @throws java.io.IOException if any
138      */
139     public void render( Map<String, ParserModule> filesToProcess, File outputDirectory, DocumentModel documentModel,
140                         DocumentRendererContext context )
141         throws DocumentRendererException, IOException
142     {
143         // nop
144     }
145 
146     /**
147      * Render a document from the files found in a source directory, depending on a rendering context.
148      *
149      * @param baseDirectory the directory containing the source files.
150      *              This should follow the standard Maven convention, ie containing all the site modules.
151      * @param outputDirectory the output directory where the document should be generated.
152      * @param documentModel the document model, containing all the metadata, etc.
153      *              If the model contains a TOC, only the files found in this TOC are rendered,
154      *              otherwise all files found under baseDirectory will be processed.
155      *              If the model is null, render all files from baseDirectory individually.
156      * @param context the rendering context when processing files.
157      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
158      * @throws java.io.IOException if any
159      * @since 1.1.2
160      */
161     public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel,
162                         DocumentRendererContext context )
163         throws DocumentRendererException, IOException
164     {
165         render( getFilesToProcess( baseDirectory ), outputDirectory, documentModel, context );
166     }
167 
168     /**
169      * Render a document from the files found in baseDirectory. This just forwards to
170      *              {@link #render(File,File,DocumentModel)} with a new DocumentModel.
171      *
172      * @param baseDirectory the directory containing the source files.
173      *              This should follow the standard Maven convention, ie containing all the site modules.
174      * @param outputDirectory the output directory where the document should be generated.
175      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
176      * @throws java.io.IOException if any
177      * @see #render(File, File, DocumentModel)
178      */
179     public void render( File baseDirectory, File outputDirectory )
180         throws DocumentRendererException, IOException
181     {
182         render( baseDirectory, outputDirectory, (DocumentModel) null );
183     }
184 
185     /**
186      * Render a document from the files found in baseDirectory.
187      *
188      * @param baseDirectory the directory containing the source files.
189      *              This should follow the standard Maven convention, ie containing all the site modules.
190      * @param outputDirectory the output directory where the document should be generated.
191      * @param documentDescriptor a file containing the document model.
192      *              If this file does not exist or is null, some default settings will be used.
193      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
194      * @throws java.io.IOException if any
195      * @see #render(File, File) if documentDescriptor does not exist or is null
196      * @see #render(Map, File, DocumentModel) otherwise
197      */
198     public void render( File baseDirectory, File outputDirectory, File documentDescriptor )
199         throws DocumentRendererException, IOException
200     {
201         if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) )
202         {
203             getLogger().warn( "No documentDescriptor found: using default settings!" );
204 
205             render( baseDirectory, outputDirectory );
206         }
207         else
208         {
209             render( getFilesToProcess( baseDirectory ), outputDirectory, readDocumentModel( documentDescriptor ),
210                     null );
211         }
212     }
213 
214     /**
215      * Render documents separately for each file found in a Map.
216      *
217      * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
218      *      source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
219      * @param outputDirectory the output directory where the documents should be generated.
220      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
221      * @throws java.io.IOException if any
222      * @since 1.1.1
223      * @deprecated since 1.1.2, use {@link #renderIndividual(Map, File, DocumentRendererContext)}
224      */
225     public void renderIndividual( Map<String, ParserModule> filesToProcess, File outputDirectory )
226         throws DocumentRendererException, IOException
227     {
228         // nop
229     }
230 
231     /**
232      * Render documents separately for each file found in a Map.
233      *
234      * @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
235      *      source files (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
236      * @param outputDirectory the output directory where the documents should be generated.
237      * @param context the rendering context.
238      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
239      * @throws java.io.IOException if any
240      * @since 1.1.2
241      */
242     public void renderIndividual( Map<String, ParserModule> filesToProcess, File outputDirectory,
243                                   DocumentRendererContext context )
244         throws DocumentRendererException, IOException
245     {
246         // nop
247     }
248 
249     /**
250      * Returns a Map of files to process. The Map contains as keys the paths of the source files
251      *      (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
252      *
253      * @param baseDirectory the directory containing the source files.
254      *              This should follow the standard Maven convention, ie containing all the site modules.
255      * @return a Map of files to process.
256      * @throws java.io.IOException in case of a problem reading the files under baseDirectory.
257      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
258      */
259     public Map<String, ParserModule> getFilesToProcess( File baseDirectory )
260         throws IOException, DocumentRendererException
261     {
262         if ( !baseDirectory.isDirectory() )
263         {
264             getLogger().warn( "No files found to process!" );
265 
266             return new HashMap<String, ParserModule>();
267         }
268 
269         setBaseDir( baseDirectory.getAbsolutePath() );
270 
271         Map<String, ParserModule> filesToProcess = new LinkedHashMap<String, ParserModule>();
272         Map<String, String> duplicatesFiles = new LinkedHashMap<String, String>();
273 
274         Collection<ParserModule> modules = parserModuleManager.getParserModules();
275         for ( ParserModule module : modules )
276         {
277             File moduleBasedir = new File( baseDirectory, module.getSourceDirectory() );
278 
279             if ( moduleBasedir.exists() )
280             {
281                 // TODO: handle in/excludes
282                 List<String> allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", null, false );
283 
284                 String[] extensions = getExtensions( module );
285                 List<String> docs = new LinkedList<String>( allFiles );
286                 // Take care of extension case
287                 for ( Iterator<String> it = docs.iterator(); it.hasNext(); )
288                 {
289                     String name = it.next().trim();
290 
291                     if ( !endsWithIgnoreCase( name, extensions ) )
292                     {
293                         it.remove();
294                     }
295                 }
296 
297                 String[] vmExtensions = new String[extensions.length];
298                 for ( int i = 0; i < extensions.length; i++ )
299                 {
300                     vmExtensions[i] = extensions[i] + ".vm";
301                 }
302                 List<String> velocityFiles = new LinkedList<String>( allFiles );
303                 // *.xml.vm
304                 for ( Iterator<String> it = velocityFiles.iterator(); it.hasNext(); )
305                 {
306                     String name = it.next().trim();
307 
308                     if ( !endsWithIgnoreCase( name, vmExtensions ) )
309                     {
310                         it.remove();
311                     }
312                 }
313                 docs.addAll( velocityFiles );
314 
315                 for ( String filePath : docs )
316                 {
317                     filePath = filePath.trim();
318 
319                     if ( filePath.lastIndexOf( '.' ) > 0 )
320                     {
321                         String key = filePath.substring( 0, filePath.lastIndexOf( '.' ) );
322 
323                         if ( duplicatesFiles.containsKey( key ) )
324                         {
325                             throw new DocumentRendererException( "Files '" + module.getSourceDirectory()
326                                 + File.separator + filePath + "' clashes with existing '"
327                                 + duplicatesFiles.get( key ) + "'." );
328                         }
329 
330                         duplicatesFiles.put( key, module.getSourceDirectory() + File.separator + filePath );
331                     }
332 
333                     filesToProcess.put( filePath, module );
334                 }
335             }
336         }
337 
338         return filesToProcess;
339     }
340 
341     protected static String[] getExtensions( ParserModule module )
342     {
343         String[] extensions = new String[module.getExtensions().length];
344         for ( int i = module.getExtensions().length - 1; i >= 0; i-- )
345         {
346             extensions[i] = '.' + module.getExtensions()[i];
347         }
348         return extensions;
349     }
350 
351     // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
352     protected static boolean endsWithIgnoreCase( String str, String searchStr )
353     {
354         if ( str.length() < searchStr.length() )
355         {
356             return false;
357         }
358 
359         return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
360     }
361 
362     protected static boolean endsWithIgnoreCase( String str, String[] searchStrs )
363     {
364         for ( String searchStr : searchStrs )
365         {
366             if ( endsWithIgnoreCase( str, searchStr ) )
367             {
368                 return true;
369             }
370         }
371         return false;
372     }
373 
374     /**
375      * Returns a Map of files to process. The Map contains as keys the paths of the source files
376      *      (relative to {@link #getBaseDir() baseDir}), and the corresponding ParserModule as values.
377      *
378      * @param files The Collection of source files.
379      * @return a Map of files to process.
380      */
381     public Map<String, ParserModule> getFilesToProcess( Collection<String> files )
382     {
383         // ----------------------------------------------------------------------
384         // Map all the file names to parser ids
385         // ----------------------------------------------------------------------
386 
387         Map<String, ParserModule> filesToProcess = new HashMap<String, ParserModule>();
388 
389         Collection<ParserModule> modules = parserModuleManager.getParserModules();
390         for ( ParserModule module : modules )
391         {
392             String[] extensions = getExtensions( module );
393 
394             String sourceDirectory = File.separator + module.getSourceDirectory() + File.separator;
395 
396             for ( String file : files )
397             {
398                 // first check if the file path contains one of the recognized source dir identifiers
399                 // (there's trouble if a pathname contains 2 identifiers), then match file extensions (not unique).
400 
401                 if ( file.indexOf( sourceDirectory ) != -1 )
402                 {
403                     filesToProcess.put( file, module );
404                 }
405                 else
406                 {
407                     // don't overwrite if it's there already
408                     if ( endsWithIgnoreCase( file, extensions ) && !filesToProcess.containsKey( file ) )
409                     {
410                         filesToProcess.put( file, module );
411                     }
412                 }
413             }
414         }
415 
416         return filesToProcess;
417     }
418 
419     /** {@inheritDoc} */
420     public DocumentModel readDocumentModel( File documentDescriptor )
421         throws DocumentRendererException, IOException
422     {
423         DocumentModel documentModel;
424 
425         Reader reader = null;
426         try
427         {
428             reader = ReaderFactory.newXmlReader( documentDescriptor );
429             documentModel = new DocumentXpp3Reader().read( reader );
430         }
431         catch ( XmlPullParserException e )
432         {
433             throw new DocumentRendererException( "Error parsing document descriptor", e );
434         }
435         finally
436         {
437             IOUtil.close( reader );
438         }
439 
440         return documentModel;
441     }
442 
443     /**
444      * Sets the current base directory.
445      *
446      * @param newDir the absolute path to the base directory to set.
447      */
448     public void setBaseDir( String newDir )
449     {
450         this.baseDir = newDir;
451     }
452 
453     /**
454      * Return the current base directory.
455      *
456      * @return the current base directory.
457      */
458     public String getBaseDir()
459     {
460         return this.baseDir;
461     }
462 
463       //--------------------------------------------
464      //
465     //--------------------------------------------
466 
467     /**
468      * Parse a source document into a sink.
469      *
470      * @param fullDocPath absolute path to the source document.
471      * @param parserId determines the parser to use.
472      * @param sink the sink to receive the events.
473      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error.
474      * @throws java.io.IOException if the source document cannot be opened.
475      * @deprecated since 1.1.2, use {@link #parse(String, String, Sink, DocumentRendererContext)}
476      */
477     protected void parse( String fullDocPath, String parserId, Sink sink )
478         throws DocumentRendererException, IOException
479     {
480         parse( fullDocPath, parserId, sink, null );
481     }
482 
483     /**
484      * Parse a source document into a sink.
485      *
486      * @param fullDocPath absolute path to the source document.
487      * @param parserId determines the parser to use.
488      * @param sink the sink to receive the events.
489      * @param context the rendering context.
490      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error.
491      * @throws java.io.IOException if the source document cannot be opened.
492      */
493     protected void parse( String fullDocPath, String parserId, Sink sink, DocumentRendererContext context )
494         throws DocumentRendererException, IOException
495     {
496         if ( getLogger().isDebugEnabled() )
497         {
498             getLogger().debug( "Parsing file " + fullDocPath );
499         }
500 
501         Reader reader = null;
502         try
503         {
504             File f = new File( fullDocPath );
505 
506             Parser parser = doxia.getParser( parserId );
507             switch ( parser.getType() )
508             {
509                 case Parser.XML_TYPE:
510                     reader = ReaderFactory.newXmlReader( f );
511 
512                     if ( isVelocityFile( f ) )
513                     {
514                         reader = getVelocityReader( f, ( (XmlStreamReader) reader ).getEncoding(), context );
515                     }
516                     if ( context != null && Boolean.TRUE.equals( (Boolean) context.get( "validate" ) ) )
517                     {
518                         reader = validate( reader, fullDocPath );
519                     }
520                     break;
521 
522                 case Parser.TXT_TYPE:
523                 case Parser.UNKNOWN_TYPE:
524                 default:
525                     if ( isVelocityFile( f ) )
526                     {
527                         reader =
528                             getVelocityReader( f, ( context == null ? ReaderFactory.FILE_ENCODING
529                                             : context.getInputEncoding() ), context );
530                     }
531                     else
532                     {
533                         if ( context == null )
534                         {
535                             reader = ReaderFactory.newPlatformReader( f );
536                         }
537                         else
538                         {
539                             reader = ReaderFactory.newReader( f, context.getInputEncoding() );
540                         }
541                     }
542             }
543 
544             sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
545 
546             doxia.parse( reader, parserId, sink );
547         }
548         catch ( ParserNotFoundException e )
549         {
550             throw new DocumentRendererException( "No parser '" + parserId
551                         + "' found for " + fullDocPath + ": " + e.getMessage(), e );
552         }
553         catch ( ParseException e )
554         {
555             throw new DocumentRendererException( "Error parsing " + fullDocPath + ": " + e.getMessage(), e );
556         }
557         finally
558         {
559             IOUtil.close( reader );
560 
561             sink.flush();
562         }
563     }
564 
565     /**
566      * Copies the contents of the resource directory to an output folder.
567      *
568      * @param outputDirectory the destination folder.
569      * @throws java.io.IOException if any.
570      */
571     protected void copyResources( File outputDirectory )
572             throws IOException
573     {
574         File resourcesDirectory = new File( getBaseDir(), "resources" );
575 
576         if ( !resourcesDirectory.isDirectory() )
577         {
578             return;
579         }
580 
581         if ( !outputDirectory.exists() )
582         {
583             outputDirectory.mkdirs();
584         }
585 
586         copyDirectory( resourcesDirectory, outputDirectory );
587     }
588 
589     /**
590      * Copy content of a directory, excluding scm-specific files.
591      *
592      * @param source directory that contains the files and sub-directories to be copied.
593      * @param destination destination folder.
594      * @throws java.io.IOException if any.
595      */
596     protected void copyDirectory( File source, File destination )
597             throws IOException
598     {
599         if ( source.isDirectory() && destination.isDirectory() )
600         {
601             DirectoryScanner scanner = new DirectoryScanner();
602 
603             String[] includedResources = {"**/**"};
604 
605             scanner.setIncludes( includedResources );
606 
607             scanner.addDefaultExcludes();
608 
609             scanner.setBasedir( source );
610 
611             scanner.scan();
612 
613             List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
614 
615             for ( String name : includedFiles )
616             {
617                 File sourceFile = new File( source, name );
618 
619                 File destinationFile = new File( destination, name );
620 
621                 FileUtils.copyFile( sourceFile, destinationFile );
622             }
623         }
624     }
625 
626     /**
627      * @param documentModel not null
628      * @return the output name defined in the documentModel without the output extension. If the output name is not
629      * defined, return target by default.
630      * @since 1.1.1
631      * @see org.apache.maven.doxia.document.DocumentModel#getOutputName()
632      * @see #getOutputExtension()
633      */
634     protected String getOutputName( DocumentModel documentModel )
635     {
636         String outputName = documentModel.getOutputName();
637         if ( outputName == null )
638         {
639             getLogger().info( "No outputName is defined in the document descriptor. Using 'target'" );
640 
641             documentModel.setOutputName( "target" );
642         }
643 
644         outputName = outputName.trim();
645         if ( outputName.toLowerCase( Locale.ENGLISH ).endsWith( "." + getOutputExtension() ) )
646         {
647             outputName =
648                 outputName.substring( 0, outputName.toLowerCase( Locale.ENGLISH )
649                                                    .lastIndexOf( "." + getOutputExtension() ) );
650         }
651         documentModel.setOutputName( outputName );
652 
653         return documentModel.getOutputName();
654     }
655 
656     /**
657      * TODO: DOXIA-111: we need a general filter here that knows how to alter the context
658      *
659      * @param f the file to process, not null
660      * @param encoding the wanted encoding, not null
661      * @param context the current render document context not null
662      * @return a reader with
663      * @throws DocumentRendererException
664      */
665     private Reader getVelocityReader( File f, String encoding, DocumentRendererContext context )
666         throws DocumentRendererException
667     {
668         if ( getLogger().isDebugEnabled() )
669         {
670             getLogger().debug( "Velocity render for " + f.getAbsolutePath() );
671         }
672 
673         SiteResourceLoader.setResource( f.getAbsolutePath() );
674 
675         Context velocityContext = new VelocityContext();
676 
677         if ( context.getKeys() != null )
678         {
679             for ( int i = 0; i < context.getKeys().length; i++ )
680             {
681                 String key = (String) context.getKeys()[i];
682 
683                 velocityContext.put( key, context.get( key ) );
684             }
685         }
686 
687         StringWriter sw = new StringWriter();
688         try
689         {
690             velocity.getEngine().mergeTemplate( f.getAbsolutePath(), encoding, velocityContext, sw );
691         }
692         catch ( Exception e )
693         {
694             throw new DocumentRendererException( "Error whenn parsing Velocity file " + f.getAbsolutePath() + ": "
695                 + e.getMessage(), e );
696         }
697 
698         return new StringReader( sw.toString() );
699     }
700 
701     /**
702      * @param f not null
703      * @return <code>true</code> if file has a vm extension, <code>false</false> otherwise.
704      */
705     private static boolean isVelocityFile( File f )
706     {
707         return FileUtils.getExtension( f.getAbsolutePath() ).toLowerCase( Locale.ENGLISH ).endsWith( "vm" );
708     }
709 
710     private Reader validate( Reader source, String resource )
711             throws ParseException, IOException
712     {
713         getLogger().debug( "Validating: " + resource );
714 
715         try
716         {
717             String content = IOUtil.toString( new BufferedReader( source ) );
718 
719             new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
720 
721             return new StringReader( content );
722         }
723         finally
724         {
725             IOUtil.close( source );
726         }
727     }
728 }