View Javadoc

1   package org.apache.maven.doxia.xsd;
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.IOException;
23  import java.io.StringReader;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import junit.framework.AssertionFailedError;
31  
32  import org.apache.maven.doxia.parser.Parser;
33  
34  import org.codehaus.plexus.PlexusTestCase;
35  import org.codehaus.plexus.logging.Logger;
36  
37  import org.xml.sax.EntityResolver;
38  import org.xml.sax.InputSource;
39  import org.xml.sax.SAXException;
40  import org.xml.sax.SAXNotRecognizedException;
41  import org.xml.sax.SAXNotSupportedException;
42  import org.xml.sax.SAXParseException;
43  import org.xml.sax.XMLReader;
44  import org.xml.sax.helpers.DefaultHandler;
45  import org.xml.sax.helpers.XMLReaderFactory;
46  
47  /**
48   * Abstract class to validate XML files.
49   *
50   * @author ltheussl
51   *
52   * @since 1.2
53   */
54  public abstract class AbstractXmlValidator
55          extends PlexusTestCase
56  {
57      protected static final String EOL = System.getProperty( "line.separator" );
58  
59      /** XMLReader to validate xml file */
60      private XMLReader xmlReader;
61  
62      /**
63       * Filter fail message.
64       *
65       * @param message not null
66       * @return <code>true</code> if the given message will fail the test.
67       * @since 1.1.1
68       */
69      protected boolean isFailErrorMessage( String message )
70      {
71          return !( message.contains( "schema_reference.4: Failed to read schema document 'http://www.w3.org/2001/xml.xsd'" )
72              || message.contains( "cvc-complex-type.4: Attribute 'alt' must appear on element 'img'." )
73              || message.contains( "cvc-complex-type.2.4.a: Invalid content starting with element" )
74              || message.contains( "cvc-complex-type.2.4.a: Invalid content was found starting with element" )
75              || message.contains( "cvc-datatype-valid.1.2.1:" ) // Doxia allow space
76              || message.contains( "cvc-attribute.3:" ) ); // Doxia allow space
77      }
78  
79      @Override
80      protected void tearDown()
81              throws Exception
82      {
83          super.tearDown();
84  
85          xmlReader = null;
86      }
87  
88      /**
89       * Validate the test documents returned by {@link #getTestDocuments()} with DTD or XSD using xerces.
90       *
91       * @throws Exception if any
92       *
93       * @see #addNamespaces(String)
94       * @see #getTestDocuments()
95       */
96      public void testValidateFiles()
97          throws Exception
98      {
99          final Logger logger = getContainer().getLoggerManager().getLoggerForComponent( Parser.ROLE );
100 
101         for ( Iterator<Map.Entry<String, String>> it = getTestDocuments().entrySet().iterator(); it.hasNext(); )
102         {
103             Map.Entry<String, String> entry = it.next();
104 
105             if ( logger.isDebugEnabled() )
106             {
107                 logger.debug( "Validate '" + entry.getKey() + "'" );
108             }
109 
110             List<ErrorMessage> errors = parseXML( entry.getValue().toString() );
111 
112             for ( Iterator<ErrorMessage> it2 = errors.iterator(); it2.hasNext(); )
113             {
114                 ErrorMessage error = it2.next();
115 
116                 if ( isFailErrorMessage( error.getMessage() ) )
117                 {
118                     fail( entry.getKey() + EOL + error.toString() );
119                 }
120                 else
121                 {
122                     if ( logger.isDebugEnabled() )
123                     {
124                         logger.debug( entry.getKey() + EOL + error.toString() );
125                     }
126                 }
127             }
128         }
129     }
130 
131     /**
132      * @param content xml content not null
133      * @return xml content with the wanted Doxia namespace
134      */
135     protected abstract String addNamespaces( String content );
136 
137     /**
138      * @return a Map &lt; filePath, fileContent &gt; of files to validate.
139      * @throws IOException if any
140      */
141     protected abstract Map<String,String> getTestDocuments()
142             throws IOException;
143 
144     /**
145      * Returns the EntityResolver that is used by the XMLReader for validation.
146      *
147      * @return an EntityResolver. Not null.
148      */
149     protected abstract EntityResolver getEntityResolver();
150 
151     // ----------------------------------------------------------------------
152     // Private methods
153     // ----------------------------------------------------------------------
154 
155     private XMLReader getXMLReader()
156     {
157         if ( xmlReader == null )
158         {
159             try
160             {
161                 xmlReader = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser" );
162                 xmlReader.setFeature( "http://xml.org/sax/features/validation", true );
163                 xmlReader.setFeature( "http://apache.org/xml/features/validation/schema", true );
164                 xmlReader.setErrorHandler( new MessagesErrorHandler() );
165                 xmlReader.setEntityResolver( getEntityResolver() );
166             }
167             catch ( SAXNotRecognizedException e )
168             {
169                 throw new AssertionFailedError( "SAXNotRecognizedException: " + e.getMessage() );
170             }
171             catch ( SAXNotSupportedException e )
172             {
173                 throw new AssertionFailedError( "SAXNotSupportedException: " + e.getMessage() );
174             }
175             catch ( SAXException e )
176             {
177                 throw new AssertionFailedError( "SAXException: " + e.getMessage() );
178             }
179         }
180 
181         ( (MessagesErrorHandler) xmlReader.getErrorHandler() ).clearMessages();
182 
183         return xmlReader;
184     }
185 
186     /**
187      * @param content
188      * @return a list of ErrorMessage
189      * @throws IOException is any
190      * @throws SAXException if any
191      */
192     private List<ErrorMessage> parseXML( String content )
193         throws IOException, SAXException
194     {
195         String xmlContent = addNamespaces( content );
196 
197         MessagesErrorHandler errorHandler = (MessagesErrorHandler) getXMLReader().getErrorHandler();
198 
199         getXMLReader().parse( new InputSource( new StringReader( xmlContent ) ) );
200 
201         return errorHandler.getMessages();
202     }
203 
204     private static class ErrorMessage
205         extends DefaultHandler
206     {
207         private final String level;
208         private final String publicID;
209         private final String systemID;
210         private final int lineNumber;
211         private final int columnNumber;
212         private final String message;
213 
214         ErrorMessage( String level, String publicID, String systemID, int lineNumber, int columnNumber,
215                              String message )
216         {
217             super();
218             this.level = level;
219             this.publicID = publicID;
220             this.systemID = systemID;
221             this.lineNumber = lineNumber;
222             this.columnNumber = columnNumber;
223             this.message = message;
224         }
225 
226         /**
227          * @return the level
228          */
229         protected String getLevel()
230         {
231             return level;
232         }
233 
234         /**
235          * @return the publicID
236          */
237         protected String getPublicID()
238         {
239             return publicID;
240         }
241         /**
242          * @return the systemID
243          */
244         protected String getSystemID()
245         {
246             return systemID;
247         }
248         /**
249          * @return the lineNumber
250          */
251         protected int getLineNumber()
252         {
253             return lineNumber;
254         }
255         /**
256          * @return the columnNumber
257          */
258         protected int getColumnNumber()
259         {
260             return columnNumber;
261         }
262         /**
263          * @return the message
264          */
265         protected String getMessage()
266         {
267             return message;
268         }
269 
270         /** {@inheritDoc} */
271         @Override
272         public String toString()
273         {
274             StringBuilder sb = new StringBuilder( 512 );
275 
276             sb.append( level ).append( EOL );
277             sb.append( "  Public ID: " ).append( publicID ).append( EOL );
278             sb.append( "  System ID: " ).append( systemID ).append( EOL );
279             sb.append( "  Line number: " ).append( lineNumber ).append( EOL );
280             sb.append( "  Column number: " ).append( columnNumber ).append( EOL );
281             sb.append( "  Message: " ).append( message ).append( EOL );
282 
283             return sb.toString();
284         }
285 
286         /** {@inheritDoc} */
287         @Override
288         public int hashCode()
289         {
290             final int prime = 31;
291             int result = 1;
292             result = prime * result + columnNumber;
293             result = prime * result + ( ( level == null ) ? 0 : level.hashCode() );
294             result = prime * result + lineNumber;
295             result = prime * result + ( ( message == null ) ? 0 : message.hashCode() );
296             result = prime * result + ( ( publicID == null ) ? 0 : publicID.hashCode() );
297             result = prime * result + ( ( systemID == null ) ? 0 : systemID.hashCode() );
298             return result;
299         }
300 
301         /** {@inheritDoc} */
302         @Override
303         public boolean equals( Object obj )
304         {
305             if ( this == obj )
306             {
307                 return true;
308             }
309             if ( obj == null )
310             {
311                 return false;
312             }
313             if ( getClass() != obj.getClass() )
314             {
315                 return false;
316             }
317             ErrorMessage other = (ErrorMessage) obj;
318             if ( columnNumber != other.getColumnNumber() )
319             {
320                 return false;
321             }
322             if ( level == null )
323             {
324                 if ( other.getLevel() != null )
325                 {
326                     return false;
327                 }
328             }
329             else if ( !level.equals( other.getLevel() ) )
330             {
331                 return false;
332             }
333             if ( lineNumber != other.getLineNumber() )
334             {
335                 return false;
336             }
337             if ( message == null )
338             {
339                 if ( other.getMessage() != null )
340                 {
341                     return false;
342                 }
343             }
344             else if ( !message.equals( other.getMessage() ) )
345             {
346                 return false;
347             }
348             if ( publicID == null )
349             {
350                 if ( other.getPublicID() != null )
351                 {
352                     return false;
353                 }
354             }
355             else if ( !publicID.equals( other.getPublicID() ) )
356             {
357                 return false;
358             }
359             if ( systemID == null )
360             {
361                 if ( other.getSystemID() != null )
362                 {
363                     return false;
364                 }
365             }
366             else if ( !systemID.equals( other.getSystemID() ) )
367             {
368                 return false;
369             }
370             return true;
371         }
372     }
373 
374     private static class MessagesErrorHandler
375         extends DefaultHandler
376     {
377         private final List<ErrorMessage> messages;
378 
379         MessagesErrorHandler()
380         {
381             messages = new ArrayList<ErrorMessage>( 8 );
382         }
383 
384         /** {@inheritDoc} */
385         @Override
386         public void warning( SAXParseException e )
387             throws SAXException
388         {
389             addMessage( "Warning", e );
390         }
391 
392         /** {@inheritDoc} */
393         @Override
394         public void error( SAXParseException e )
395             throws SAXException
396         {
397             addMessage( "Error", e );
398         }
399 
400         /** {@inheritDoc} */
401         @Override
402         public void fatalError( SAXParseException e )
403             throws SAXException
404         {
405             addMessage( "Fatal error", e );
406         }
407 
408         private void addMessage( String pre, SAXParseException e )
409         {
410             ErrorMessage error =
411                 new ErrorMessage( pre, e.getPublicId(), e.getSystemId(), e.getLineNumber(), e.getColumnNumber(),
412                                   e.getMessage() );
413 
414             messages.add( error );
415         }
416 
417         protected List<ErrorMessage> getMessages()
418         {
419             return Collections.unmodifiableList( messages );
420         }
421 
422         protected void clearMessages()
423         {
424             messages.clear();
425         }
426     }
427 }