KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > xdoclet > util > XmlValidator


1 /*
2  * Copyright (c) 2001, 2002 The XDoclet team
3  * All rights reserved.
4  */

5 package xdoclet.util;
6
7 import java.io.File JavaDoc;
8 import java.io.FileReader JavaDoc;
9 import java.io.IOException JavaDoc;
10 import java.io.StringReader JavaDoc;
11 import java.net.URL JavaDoc;
12 import java.util.ArrayList JavaDoc;
13 import java.util.Collection JavaDoc;
14 import java.util.HashMap JavaDoc;
15
16 import javax.xml.parsers.ParserConfigurationException JavaDoc;
17 import javax.xml.parsers.SAXParser JavaDoc;
18 import javax.xml.parsers.SAXParserFactory JavaDoc;
19 import org.apache.commons.logging.Log;
20
21 import org.apache.tools.ant.AntClassLoader;
22
23 import org.xml.sax.InputSource JavaDoc;
24 import org.xml.sax.Parser JavaDoc;
25 import org.xml.sax.SAXException JavaDoc;
26 import org.xml.sax.SAXNotRecognizedException JavaDoc;
27 import org.xml.sax.SAXNotSupportedException JavaDoc;
28 import org.xml.sax.SAXParseException JavaDoc;
29 import org.xml.sax.XMLReader JavaDoc;
30 import org.xml.sax.helpers.DefaultHandler JavaDoc;
31 import org.xml.sax.helpers.ParserAdapter JavaDoc;
32 import xdoclet.XDocletException;
33 import xdoclet.XDocletMessages;
34
35 /**
36  * This handler implementation is capable of providing dtds from a local storage instead of accessing them over the net.
37  * Further, it will throw an exception if the parsed xml is not acording to the DTD it specifies.
38  *
39  * @author <a HREF="mailto:aslak.nospam@users.sf.net">Aslak Helles�y</a>
40  * @created September 18, 2001
41  * @version $Revision: 1.19 $
42  * @todo Deal with Translator.getString()'s exception better in resolveEntity(String, String)
43  */

44 public class XmlValidator extends DefaultHandler JavaDoc
45 {
46
47     /**
48      * The crimson implementation is shipped with ant.
49      */

50     public final static String JavaDoc DEFAULT_XML_READER_CLASSNAME = "org.apache.crimson.parser.XMLReaderImpl";
51     private final static String JavaDoc JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
52     private final static String JavaDoc JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
53     private final static String JavaDoc W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
54
55     private static XmlValidator instance = new XmlValidator(null);
56
57     protected ClassLoader JavaDoc classLoader;
58
59     /**
60      * XMLReader used for validation
61      */

62     protected XMLReader JavaDoc xmlReader = null;
63
64     protected String JavaDoc readerClassName = DEFAULT_XML_READER_CLASSNAME;
65
66     private final HashMap JavaDoc _dtds = new HashMap JavaDoc();
67     private final Collection JavaDoc _schemas = new ArrayList JavaDoc();
68
69     /**
70      * Describe what the XmlValidator constructor does
71      *
72      * @param classLoader Describe what the parameter does
73      */

74     public XmlValidator(ClassLoader JavaDoc classLoader)
75     {
76         this.classLoader = classLoader;
77     }
78
79     /**
80      * Gets the Instance attribute of the XmlValidator class
81      *
82      * @return The Instance value
83      */

84     public static XmlValidator getInstance()
85     {
86         return instance;
87     }
88
89     /**
90      * Sets the Instance attribute of the XmlValidator class
91      *
92      * @param instance The new Instance value
93      */

94     public static void setInstance(XmlValidator instance)
95     {
96         XmlValidator.instance = instance;
97     }
98
99     /**
100      * Registers a local DTD document by its public id. This is necessary to avoid DTD loading over the net.
101      *
102      * @param publicId the publicId of the DTD
103      * @param dtdURL the URL of the local DTD, which must be loadable by the class passed in the constructor. This
104      * URL typically points inside a local jar file
105      */

106     public void registerDTD(String JavaDoc publicId, URL JavaDoc dtdURL)
107     {
108         Log log = LogUtil.getLog(XmlValidator.class, "registerDTD");
109
110         if (log.isDebugEnabled()) {
111             log.debug("DTD '" + dtdURL + "' registered for public Id '" + publicId + "'.");
112         }
113
114         _dtds.put(publicId, dtdURL);
115     }
116
117     /**
118      * Registers a local XSD document by its public id. This is necessary to avoid loading XML Schemas over the net.
119      *
120      * @param schemaURL
121      */

122     public void registerSchema(URL JavaDoc schemaURL)
123     {
124         Log log = LogUtil.getLog(XmlValidator.class, "registerSchema");
125
126         if (log.isDebugEnabled()) {
127             log.debug("Schema '" + schemaURL + "' registered.");
128         }
129
130         _schemas.add(schemaURL.toString());
131     }
132
133     /**
134      * Called by parser when a DTD declaration is encountered in the parsed XML document
135      *
136      * @param publicId the public id of the DTD
137      * @param systemId the system id of the DTD
138      * @return an InputSource from containing the DTD document, provided it has been previously registered via
139      * the {@link #registerDTD} method. If not, null will be returned, and the parser will atempt to load the DTD
140      * from the systemId value, Usually an Internet http URL.
141      */

142     public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId)
143     {
144         Log log = LogUtil.getLog(XmlValidator.class, "resolveEntity");
145
146         if (log.isDebugEnabled()) {
147             log.debug("publicId=" + publicId);
148             log.debug("systemId=" + systemId);
149         }
150
151         URL JavaDoc dtdURL = (URL JavaDoc) _dtds.get(publicId);
152
153         if (dtdURL != null) {
154             String JavaDoc dtd = FileManager.getURLContent(dtdURL);
155
156             if (log.isDebugEnabled()) {
157                 log.debug("dtdURL != null, dtdURL=" + dtdURL);
158                 log.debug("dtd.length()=" + dtd.length());
159             }
160
161             return new InputSource JavaDoc(new StringReader JavaDoc(dtd));
162         }
163         else {
164             log.debug("dtdURL == null");
165
166             String JavaDoc msg = Translator.getString(XDocletMessages.class, XDocletMessages.COULDNT_LOAD_LOCAL_DTD, new String JavaDoc[]{publicId});
167
168             // no error if we are doing schema validation
169
if (isSchemaValidation()) {
170                 log.debug(msg);
171             }
172             else {
173                 log.error(msg);
174             }
175             return null;
176         }
177     }
178
179     /**
180      * Called by parser if a error occurs
181      *
182      * @param e an exception describing the error
183      * @throws SAXParseException every time this method is called by the parser
184      */

185     public void error(SAXParseException JavaDoc e)
186          throws SAXParseException JavaDoc
187     {
188         throw e;
189     }
190
191     /**
192      * Called by parser if a warning occurs
193      *
194      * @param e an exception describing the warning
195      * @throws SAXParseException every time this method is called by the parser
196      */

197     public void warning(SAXParseException JavaDoc e)
198          throws SAXParseException JavaDoc
199     {
200 // throw e;
201
}
202
203     /**
204      * Validates an XML file for conformance to a declared DTD or XMLSchema. This method is useful for subclasses that
205      * wish to verify that a generated XML file is ok. Please note that the callers should make sure to register any
206      * DTDs required for validation on the handler object.
207      *
208      * @param xmlFile Description of Parameter
209      * @exception XDocletException Description of Exception
210      */

211     public void validate(File JavaDoc xmlFile) throws XDocletException
212     {
213         if (classLoader == null) {
214             // we're running in forked mode. no need to use ant classloader hack
215
// Get strange org.xml.sax.SAXParseException: Declared encoding "UTF-8" does not match actual one "Cp1252"; this might not be an error.
216
initValidator();
217         }
218         else {
219             initValidatorHack();
220         }
221         doValidate(xmlFile);
222     }
223
224     /**
225      * Resets the Validator
226      */

227     public void reset()
228     {
229         _schemas.clear();
230         _dtds.clear();
231     }
232
233     /**
234      * Returns whether this validator should validate against a XML Schema
235      *
236      * @return <code>true</code> it the validator should validate against a XSD
237      */

238     private boolean isSchemaValidation()
239     {
240         return !_schemas.isEmpty();
241     }
242
243     /*
244      * parse the file
245      */

246     /**
247      * Describe what the method does
248      *
249      * @param xml_file Describe what the parameter does
250      * @exception XDocletException Describe the exception
251      */

252     private void doValidate(File JavaDoc xml_file) throws XDocletException
253     {
254         Log log = LogUtil.getLog(XmlValidator.class, "doValidate");
255
256         try {
257             if (log.isDebugEnabled()) {
258                 log.debug("Validating " + xml_file.getName() + "... ");
259             }
260
261             //errorHandler.init( afile );
262
InputSource JavaDoc is = new InputSource JavaDoc(new FileReader JavaDoc(xml_file));
263             String JavaDoc uri = "file:" + xml_file.getAbsolutePath().replace('\\', '/');
264
265             for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) {
266                 uri = uri.substring(0, index) + "%23" + uri.substring(index + 1);
267             }
268
269             is.setSystemId(uri);
270
271             xmlReader.parse(is);
272         }
273         catch (SAXParseException JavaDoc e) {
274             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.GENERATED_XML_INVALID,
275                 new String JavaDoc[]{e.getSystemId(), Integer.toString(e.getLineNumber()), e.getMessage()});
276
277             throw new XDocletException(e, message);
278         }
279         catch (SAXException JavaDoc e) {
280             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.PARSING_FAILED,
281                 new String JavaDoc[]{xml_file.getAbsolutePath(), e.getMessage()});
282
283             throw new XDocletException(e, message);
284         }
285         catch (IOException JavaDoc e) {
286             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.PARSING_FAILED,
287                 new String JavaDoc[]{xml_file.getAbsolutePath(), e.getMessage()});
288
289             throw new XDocletException(e, message);
290         }
291
292 // if( errorHandler.getFailure() )
293
// {
294
// if( failOnError )
295
// throw new BuildException( xml_file + " is not a valid XML document." );
296
// else
297
// log( xml_file + " is not a valid XML document", Project.MSG_ERR );
298
// }
299
}
300
301     /**
302      * Describe what the method does
303      *
304      * @exception XDocletException Describe the exception
305      */

306     private void initValidatorHack() throws XDocletException
307     {
308         Log log = LogUtil.getLog(XmlValidator.class, "initValidator");
309
310         // The crimson parser does not support schema validation
311
if (isSchemaValidation()) {
312             log.warn(Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.PARSER_DOES_NOT_SUPPORT_XSD_VALIDATION));
313         }
314
315         try {
316             // load the parser class
317
// with JAXP, we would use a SAXParser factory
318
Class JavaDoc readerClass = null;
319
320             if (classLoader != null) {
321                 readerClass = classLoader.loadClass(readerClassName);
322                 AntClassLoader.initializeClass(readerClass);
323             }
324             else {
325                 readerClass = Class.forName(readerClassName);
326             }
327
328             // then check it implements XMLReader
329
if (XMLReader JavaDoc.class.isAssignableFrom(readerClass)) {
330                 xmlReader = (XMLReader JavaDoc) readerClass.newInstance();
331
332                 if (log.isDebugEnabled()) {
333                     log.debug("Using SAX2 reader " + readerClassName);
334                 }
335             }
336             else {
337                 // see if it is a SAX1 Parser
338
if (Parser JavaDoc.class.isAssignableFrom(readerClass)) {
339                     Parser JavaDoc parser = (Parser JavaDoc) readerClass.newInstance();
340
341                     xmlReader = new ParserAdapter JavaDoc(parser);
342
343                     if (log.isDebugEnabled()) {
344                         log.debug("Using SAX1 parser " + readerClassName);
345                     }
346                 }
347                 else {
348                     String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.INIT_FAILED, new String JavaDoc[]{readerClassName});
349
350                     System.out.println("init_failed");
351
352                     throw new XDocletException(message);
353                 }
354             }
355         }
356         catch (ClassNotFoundException JavaDoc e) {
357             e.printStackTrace();
358
359             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.INIT_FAILED, new String JavaDoc[]{readerClassName});
360
361             throw new XDocletException(e, message);
362         }
363         catch (InstantiationException JavaDoc e) {
364             e.printStackTrace();
365
366             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.INIT_FAILED, new String JavaDoc[]{readerClassName});
367
368             throw new XDocletException(e, message);
369         }
370         catch (IllegalAccessException JavaDoc e) {
371             e.printStackTrace();
372
373             String JavaDoc message = Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.INIT_FAILED, new String JavaDoc[]{readerClassName});
374
375             throw new XDocletException(e, message);
376         }
377
378         //\\xmlReader.setErrorHandler( errorHandler );
379
xmlReader.setEntityResolver(this);
380
381         if (!(xmlReader instanceof ParserAdapter JavaDoc)) {
382             // turn validation on
383
try {
384                 xmlReader.setFeature("http://xml.org/sax/features/validation", true);
385             }
386             catch (SAXNotRecognizedException JavaDoc e) {
387                 e.printStackTrace();
388             }
389             catch (SAXNotSupportedException JavaDoc e) {
390                 e.printStackTrace();
391             }
392         }
393     }
394
395     private void initValidator() throws XDocletException
396     {
397         Log log = LogUtil.getLog(XmlValidator.class, "initValidator");
398
399         try {
400             SAXParserFactory JavaDoc factory = SAXParserFactory.newInstance();
401
402             factory.setValidating(true);
403             factory.setNamespaceAware(isSchemaValidation());
404
405             // try to get a SAX Parser
406
SAXParser JavaDoc parser;
407
408             try {
409                 parser = factory.newSAXParser();
410                 if (log.isDebugEnabled()) {
411                     log.debug("SAX Parser crated class=" + parser.getClass());
412                 }
413             }
414             catch (ParserConfigurationException JavaDoc e) {
415                 // try to get a non-namespace-aware parser if a namespace-aware parser was
416
// required and none could be found
417
if (!factory.isNamespaceAware()) {
418                     throw e;
419                 }
420                 factory.setNamespaceAware(false);
421                 parser = factory.newSAXParser();
422                 log.warn(Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.NO_NAMESPACE_AWARE_SAX_PARSER));
423             }
424
425             // try to setup XML Schema Validation if schema validation is required and
426
// a namespace-aware SAX parser is available
427
if (isSchemaValidation() && parser.isNamespaceAware()) {
428                 try {
429                     parser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
430                     parser.setProperty(JAXP_SCHEMA_SOURCE, _schemas.toArray(new String JavaDoc[_schemas.size()]));
431                 }
432                 catch (SAXException JavaDoc e) {
433                     // no validation at all if schema validation is requested but not supported by the parser
434
// to avoid errors when the parser tries to find DTD's which are not available
435
factory.setValidating(false);
436                     parser = factory.newSAXParser();
437                     log.warn(Translator.getString(XDocletUtilMessages.class, XDocletUtilMessages.PARSER_DOES_NOT_SUPPORT_XSD_VALIDATION));
438                     log.debug("JAXP 1.2 schema validation properties not recognized", e);
439                 }
440             }
441             xmlReader = parser.getXMLReader();
442             xmlReader.setEntityResolver(this);
443             xmlReader.setErrorHandler(this);
444
445         }
446         catch (SAXException JavaDoc e) {
447             throw new XDocletException(e, Translator.getString(XDocletMessages.class, XDocletMessages.COULDNT_INIT_XML_PARSER));
448         }
449         catch (ParserConfigurationException JavaDoc e) {
450             throw new XDocletException(e, Translator.getString(XDocletMessages.class, XDocletMessages.COULDNT_CONF_XML_PARSER));
451         }
452         catch (NullPointerException JavaDoc e) {
453             throw new XDocletException(e, Translator.getString(XDocletMessages.class, XDocletMessages.COULDNT_LOAD_DTD));
454         }
455     }
456 }
457
Popular Tags