View Javadoc

1   /* ====================================================================
2    * The Apache Software License, Version 1.1
3    *
4    * Copyright (c) 2003 The Apache Software Foundation.  All rights
5    * reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer.
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution,
20   *    if any, must include the following acknowledgment:
21   *       "This product includes software developed by the
22   *        Apache Software Foundation (http://www.apache.org )."
23   *    Alternately, this acknowledgment may appear in the software itself,
24   *    if and wherever such third-party acknowledgments normally appear.
25   *
26   * 4. The names "Apache" and "Apache Software Foundation" and
27   *    "Apache Maven" must not be used to endorse or promote products
28   *    derived from this software without prior written permission. For
29   *    written permission, please contact apache@apache.org.
30   *
31   * 5. Products derived from this software may not be called "Apache",
32   *    "Apache Maven", nor may "Apache" appear in their name, without
33   *    prior written permission of the Apache Software Foundation.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46   * SUCH DAMAGE.
47   * ====================================================================
48   *
49   * This software consists of voluntary contributions made by many
50   * individuals on behalf of the Apache Software Foundation.  For more
51   * information on the Apache Software Foundation, please see
52   * <http://www.apache.org >.
53   *
54   * ====================================================================
55   */
56  
57  package org.apache.maven.sitevalidator;
58  
59  import java.io.File;
60  import java.io.FileInputStream;
61  import java.io.FileNotFoundException;
62  import java.io.FileOutputStream;
63  import java.io.IOException;
64  import java.io.UnsupportedEncodingException;
65  import java.net.UnknownHostException;
66  import java.util.ArrayList;
67  import java.util.Iterator;
68  import java.util.List;
69  
70  import org.apache.tools.ant.BuildException;
71  import org.apache.tools.ant.DirectoryScanner;
72  import org.apache.tools.ant.Project;
73  import org.apache.tools.ant.Task;
74  import org.apache.tools.ant.types.DTDLocation;
75  import org.apache.tools.ant.types.FileSet;
76  import org.apache.tools.ant.types.XMLCatalog;
77  import org.dom4j.Element;
78  import org.dom4j.io.OutputFormat;
79  import org.dom4j.io.XMLWriter;
80  import org.dom4j.tree.DefaultElement;
81  import org.xml.sax.InputSource;
82  import org.xml.sax.SAXException;
83  import org.xml.sax.SAXNotRecognizedException;
84  import org.xml.sax.SAXNotSupportedException;
85  import org.xml.sax.XMLReader;
86  import org.xml.sax.helpers.XMLReaderFactory;
87  
88  /***
89   * Validator task.
90   * This task is a modified version of the standard ant validator task, specialized for checking
91   * xhtml files and logging errors (without beaking the build)
92   * @author fgiust
93   * @version $Revision: 1.4 $ ($Author: fgiust $)
94   */
95  public class Validator extends Task
96  {
97      /***
98       * validation feature
99       */
100     private static final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation";
101 
102     /***
103      * encoding - defaults to ISO-8859-1
104      */
105     private String encoding = "ISO-8859-1";
106 
107     /***
108      * output file
109      */
110     private File outputFile = null;
111 
112     /***
113      * XmlWriter used to write to outputFile
114      */
115     private XMLWriter out;
116 
117     /***
118      * sets of file to be validated
119      */
120     private List filesets = new ArrayList();
121 
122     /***
123      * XMLReader used to validation process
124      */
125     private XMLReader xmlReader = null;
126 
127     /***
128      * Validator handler: implements both ErrorHandler and DTDHandler
129      */
130     private ValidatorHandler validatorHandler = new ValidatorHandler();
131 
132     /***
133      * XMLCatalog containing references to local dtds and entities
134      */
135     private XMLCatalog xmlCatalog = new XMLCatalog();
136 
137     /***
138      * sets the input/output files encoding
139      * @param value encoding
140      */
141     public void setEncoding(String value)
142     {
143         encoding = value;
144     }
145 
146     /***
147      * sets the output file
148      * @param file output file
149      */
150     public void setOUtput(File file)
151     {
152         outputFile = file;
153     }
154 
155     /***
156      * add an XMLCatalog as a nested element.
157      * @param catalog XMLCatalog
158      */
159     public void addConfiguredXMLCatalog(XMLCatalog catalog)
160     {
161         xmlCatalog.addConfiguredXMLCatalog(catalog);
162     }
163 
164     /***
165      * specify a set of file to be checked
166      * @param set Fileset
167      */
168     public void addFileset(FileSet set)
169     {
170         filesets.add(set);
171     }
172 
173     /***
174      * initialize task
175      */
176     public void init()
177     {
178         super.init();
179         xmlCatalog.setProject(project);
180     }
181 
182     /***
183      * Create a DTD location record.
184      * This stores the location of a DTD. The DTD is identified by its public Id
185      * @return DTDLocation
186      */
187     public DTDLocation createDTD()
188     {
189         DTDLocation dtdLocation = new DTDLocation();
190         xmlCatalog.addDTD(dtdLocation);
191         return dtdLocation;
192     }
193 
194     /***
195      * Init the parser. Load the parser class and enable validation
196      */
197     private void initValidator()
198     {
199 
200         // turn validation on
201         try
202         {
203             xmlReader = XMLReaderFactory.createXMLReader();
204             xmlReader.setFeature(VALIDATION_FEATURE, true);
205 
206         }
207         catch (SAXNotRecognizedException e)
208         {
209             throw new BuildException("Could not start validation: " + xmlReader + " doesn't provide validation");
210         }
211         catch (SAXNotSupportedException e)
212         {
213             throw new BuildException("Could not start validation: " + xmlReader + " doesn't provide validation");
214         }
215         catch (SAXException e)
216         {
217             throw new BuildException("Could not start validation: " + xmlReader + " not found");
218         }
219 
220         log(xmlReader.getClass().getName(), Project.MSG_DEBUG);
221 
222         // set entity resolver
223         xmlReader.setEntityResolver(xmlCatalog);
224 
225         // set handler for validation messages
226         xmlReader.setErrorHandler(validatorHandler);
227         xmlReader.setEntityResolver(validatorHandler);
228 
229     }
230 
231     /***
232      * execute the task
233      * @throws BuildException if unable to build a validating SAX2 parser
234      */
235     public void execute() throws BuildException
236     {
237 
238         // check output dir
239         File dir = outputFile.getParentFile();
240 
241         // create output dir if missing
242         if (dir != null)
243         {
244             dir.mkdirs();
245         }
246 
247         // set output format
248         OutputFormat format = OutputFormat.createPrettyPrint();
249         format.setEncoding(encoding);
250 
251         // create the XMLWriter
252         try
253         {
254             out = new XMLWriter(new FileOutputStream(outputFile), format);
255         }
256         catch (UnsupportedEncodingException e)
257         {
258             throw new BuildException("Unsopported encoding: [" + encoding + "]");
259         }
260         catch (FileNotFoundException e)
261         {
262             throw new BuildException("Unable to write to [" + outputFile.getAbsolutePath() + "]");
263         }
264 
265         // init validator
266         initValidator();
267 
268         // pass out to validator handler
269         validatorHandler.setOut(out);
270 
271         // create the root element
272         Element validation = new DefaultElement("validation");
273         try
274         {
275             // start xml document and open the root element
276             out.startDocument();
277             out.writeOpen(validation);
278 
279             // create the root element
280 
281             // iterates on filesets
282             Iterator fileIterator = filesets.iterator();
283 
284             while (fileIterator.hasNext())
285             {
286                 // next fileset
287                 FileSet fs = (FileSet) fileIterator.next();
288 
289                 // scan for files
290                 DirectoryScanner ds = fs.getDirectoryScanner(project);
291 
292                 // get all the file names
293                 String[] files = ds.getIncludedFiles();
294 
295                 // iterates on files
296                 for (int j = 0; j < files.length; j++)
297                 {
298                     // new file element
299                     Element xmlfile = new DefaultElement("file");
300 
301                     // create a new File
302                     File srcFile = new File(fs.getDir(project), files[j]);
303 
304                     // add file name to element
305                     xmlfile.addAttribute("name", files[j]);
306 
307                     // open file element
308                     out.writeOpen(xmlfile);
309 
310                     // perform validation
311                     doValidate(srcFile);
312 
313                     // close file element
314                     out.writeClose(xmlfile);
315                 }
316             }
317 
318             // close the root element
319             out.writeClose(validation);
320 
321             // end document
322             out.endDocument();
323         }
324 
325         catch (SAXException e)
326         {
327             log(e.getClass() + " " + e.getMessage(), Project.MSG_WARN);
328             throw new BuildException(e.getMessage());
329         }
330         catch (IOException e)
331         {
332             log(e.getClass() + " " + e.getMessage(), Project.MSG_WARN);
333             throw new BuildException(e.getMessage());
334         }
335 
336         finally
337         {
338             try
339             {
340                 // be sure out is closed
341                 if (out != null)
342                 {
343                     out.close();
344                 }
345             }
346             catch (IOException e)
347             {
348                 e.printStackTrace();
349             }
350         }
351     }
352 
353     /***
354      * parse the file
355      * @param afile File to validate
356      */
357     private void doValidate(File afile)
358     {
359         log("Validating " + afile.getName() + "... ", Project.MSG_INFO);
360 
361         // init validation handler
362         validatorHandler.init(afile);
363 
364         try
365         {
366 
367             InputSource is = new InputSource(new FileInputStream(afile));
368             String uri = "file:" + afile.getAbsolutePath().replace('//', '/');
369             for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#'))
370             {
371                 uri = uri.substring(0, index) + "%23" + uri.substring(index + 1);
372             }
373             is.setSystemId(uri);
374             is.setEncoding(encoding);
375 
376             xmlReader.parse(is);
377         }
378         catch (SAXException e)
379         {
380             Element error = new DefaultElement("fatalerror");
381             error.addText(e.getMessage());
382             writeToLog(error);
383 
384         }
385         catch (UnknownHostException e)
386         {
387             Element error = new DefaultElement("fatalerror");
388 
389             error.addText(
390                 "Could not validate document " + afile + "; dtd missing from repository and unable to connect");
391             writeToLog(error);
392         }
393         catch (IOException e)
394         {
395             Element error = new DefaultElement("fatalerror");
396             error.addText(e.getMessage());
397             writeToLog(error);
398 
399         }
400 
401         if (validatorHandler.getErrors() > 0)
402         {
403             log(afile + ", errors=" + validatorHandler.getErrors(), Project.MSG_INFO);
404         }
405 
406     }
407 
408     /***
409      * write an element to the xml log file
410      * @param element Element to write
411      */
412     private void writeToLog(Element element)
413     {
414         try
415         {
416             out.write(element);
417         }
418         catch (IOException e)
419         {
420             e.printStackTrace();
421         }
422     }
423 
424 }