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 }