001 package com.thaiopensource.validate; 002 003 import org.xml.sax.SAXException; 004 import org.xml.sax.InputSource; 005 import org.xml.sax.DTDHandler; 006 import org.xml.sax.XMLReader; 007 import org.xml.sax.ErrorHandler; 008 009 import java.io.IOException; 010 import java.io.File; 011 import java.net.MalformedURLException; 012 013 import com.thaiopensource.util.UriOrFile; 014 import com.thaiopensource.util.PropertyMap; 015 import com.thaiopensource.util.PropertyMapBuilder; 016 import com.thaiopensource.util.PropertyId; 017 import com.thaiopensource.xml.sax.XMLReaderCreator; 018 import com.thaiopensource.xml.sax.CountingErrorHandler; 019 import com.thaiopensource.xml.sax.Jaxp11XMLReaderCreator; 020 import com.thaiopensource.xml.sax.ErrorHandlerImpl; 021 import com.thaiopensource.validate.auto.AutoSchemaReader; 022 023 /** 024 * Provides a simplified API for validating XML documents against schemas. 025 * This class is neither reentrant nor safe for access from multiple threads. 026 * 027 * @author <a href="mailto:jjc@jclark.com">James Clark</a> 028 */ 029 030 public class ValidationDriver { 031 private static final PropertyId[] requiredProperties = { 032 ValidateProperty.XML_READER_CREATOR, 033 ValidateProperty.ERROR_HANDLER 034 }; 035 036 private static final Class[] defaultClasses = { 037 Jaxp11XMLReaderCreator.class, 038 ErrorHandlerImpl.class 039 }; 040 041 private final XMLReaderCreator xrc; 042 private XMLReader xr; 043 private final CountingErrorHandler eh; 044 private final SchemaReader sr; 045 private final PropertyMap schemaProperties; 046 private final PropertyMap instanceProperties; 047 private Validator validator; 048 private Schema schema; 049 050 /** 051 * Creates and initializes a ValidationDriver. 052 * 053 * @param schemaProperties a PropertyMap specifying properties controlling schema creation; 054 * must not be <code>null</code> 055 * @param instanceProperties a PropertyMap specifying properties controlling validation; 056 * must not be <code>null</code> 057 * @param schemaReader the SchemaReader to use; if this is <code>null</code>, then the schema 058 * must be in XML, and the namespace URI of the root element will be used to determine what 059 * the schema language is 060 */ 061 public ValidationDriver(PropertyMap schemaProperties, 062 PropertyMap instanceProperties, 063 SchemaReader schemaReader) { 064 PropertyMapBuilder builder = new PropertyMapBuilder(schemaProperties); 065 for (int i = 0; i < requiredProperties.length; i++) { 066 if (!builder.contains(requiredProperties[i])) { 067 try { 068 builder.put(requiredProperties[i], 069 defaultClasses[i].newInstance()); 070 } 071 catch (InstantiationException e) { 072 } 073 catch (IllegalAccessException e) { 074 } 075 } 076 } 077 this.schemaProperties = builder.toPropertyMap(); 078 builder = new PropertyMapBuilder(instanceProperties); 079 for (int i = 0; i < requiredProperties.length; i++) { 080 if (!builder.contains(requiredProperties[i])) 081 builder.put(requiredProperties[i], 082 this.schemaProperties.get(requiredProperties[i])); 083 } 084 eh = new CountingErrorHandler((ErrorHandler)builder.get(ValidateProperty.ERROR_HANDLER)); 085 ValidateProperty.ERROR_HANDLER.put(builder, eh); 086 this.instanceProperties = builder.toPropertyMap(); 087 this.xrc = ValidateProperty.XML_READER_CREATOR.get(this.instanceProperties); 088 this.sr = schemaReader == null ? new AutoSchemaReader() : schemaReader; 089 } 090 091 /** 092 * Equivalent to ValidationDriver(schemaProperties, instanceProperties, null). 093 * 094 * @see #ValidationDriver(PropertyMap,PropertyMap,SchemaReader) 095 */ 096 public ValidationDriver(PropertyMap schemaProperties, PropertyMap instanceProperties) { 097 this(schemaProperties, instanceProperties, null); 098 } 099 100 /** 101 * Equivalent to ValidationDriver(properties, properties, sr). 102 * 103 * @see #ValidationDriver(PropertyMap,PropertyMap,SchemaReader) 104 */ 105 public ValidationDriver(PropertyMap properties, SchemaReader sr) { 106 this(properties, properties, sr); 107 } 108 109 /** 110 * Equivalent to ValidationDriver(properties, properties, null). 111 * 112 * @see #ValidationDriver(PropertyMap,PropertyMap,SchemaReader) 113 */ 114 public ValidationDriver(PropertyMap properties) { 115 this(properties, properties, null); 116 } 117 118 /** 119 * Equivalent to ValidationDriver(PropertyMap.EMPTY, PropertyMap.EMPTY, null). 120 * 121 * @see #ValidationDriver(PropertyMap,PropertyMap,SchemaReader) 122 */ 123 public ValidationDriver(SchemaReader sr) { 124 this(PropertyMap.EMPTY, sr); 125 } 126 127 /** 128 * Equivalent to ValidationDriver(PropertyMap.EMPTY, PropertyMap.EMPTY, null). 129 * 130 * @see #ValidationDriver(PropertyMap,PropertyMap,SchemaReader) 131 */ 132 public ValidationDriver() { 133 this(PropertyMap.EMPTY, PropertyMap.EMPTY, null); 134 } 135 136 /** 137 * Loads a schema. Subsequent calls to <code>validate</code> will validate with 138 * respect the loaded schema. This can be called more than once to allow 139 * multiple documents to be validated against different schemas. 140 * 141 * @param in the InputSource for the schema 142 * @return <code>true</code> if the schema was loaded successfully; <code>false</code> otherwise 143 * @throws IOException if an I/O error occurred 144 * @throws SAXException if an XMLReader or ErrorHandler threw a SAXException 145 */ 146 public boolean loadSchema(InputSource in) throws SAXException, IOException { 147 try { 148 schema = sr.createSchema(in, schemaProperties); 149 validator = null; 150 return true; 151 } 152 catch (IncorrectSchemaException e) { 153 return false; 154 } 155 } 156 157 /** 158 * Validates a document against the currently loaded schema. This can be called 159 * multiple times in order to validate multiple documents. 160 * 161 * @param in the InputSource for the document to be validated 162 * @return <code>true</code> if the document is valid; <code>false</code> otherwise 163 * @throws java.lang.IllegalStateException if there is no currently loaded schema 164 * @throws java.io.IOException if an I/O error occurred 165 * @throws org.xml.sax.SAXException if an XMLReader or ErrorHandler threw a SAXException 166 */ 167 public boolean validate(InputSource in) throws SAXException, IOException { 168 if (schema == null) 169 throw new IllegalStateException("cannot validate without schema"); 170 if (validator == null) 171 validator = schema.createValidator(instanceProperties); 172 if (xr == null) { 173 xr = xrc.createXMLReader(); 174 xr.setErrorHandler(eh); 175 } 176 eh.reset(); 177 xr.setContentHandler(validator.getContentHandler()); 178 DTDHandler dh = validator.getDTDHandler(); 179 if (dh != null) 180 xr.setDTDHandler(dh); 181 try { 182 xr.parse(in); 183 return !eh.getHadErrorOrFatalError(); 184 } 185 finally { 186 validator.reset(); 187 } 188 } 189 190 /** 191 * Returns an <code>InputSource</code> for a filename. 192 * 193 * @param filename a String specifying the filename 194 * @return an <code>InputSource</code> for the filename 195 */ 196 static public InputSource fileInputSource(String filename) throws MalformedURLException { 197 return ValidationDriver.fileInputSource(new File(filename)); 198 } 199 200 /** 201 * Returns an <code>InputSource</code> for a <code>File</code>. 202 * 203 * @param file the <code>File</code> 204 * @return an <code>InputSource</code> for the filename 205 */ 206 static public InputSource fileInputSource(File file) throws MalformedURLException { 207 return new InputSource(UriOrFile.fileToUri(file)); 208 } 209 210 /** 211 * Returns an <code>InputSource</code> for a string that represents either a file 212 * or an absolute URI. If the string looks like an absolute URI, it will be 213 * treated as an absolute URI, otherwise it will be treated as a filename. 214 * 215 * @param uriOrFile a <code>String</code> representing either a file or an absolute URI 216 * @return an <code>InputSource</code> for the file or absolute URI 217 */ 218 static public InputSource uriOrFileInputSource(String uriOrFile) throws MalformedURLException { 219 return new InputSource(UriOrFile.toUri(uriOrFile)); 220 } 221 }