1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package javax.jdo.util;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileReader;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.IOException;
25 import java.io.FileFilter;
26 import java.io.FilenameFilter;
27
28 import java.security.AccessController;
29 import java.security.PrivilegedAction;
30 import java.util.Arrays;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.StringTokenizer;
37
38 import javax.jdo.JDOFatalException;
39
40 import javax.xml.parsers.*;
41 import org.w3c.dom.Document;
42 import org.xml.sax.*;
43 import org.xml.sax.helpers.*;
44
45 /***
46 * Tests schema files.
47 * <p>
48 */
49 public class XMLTestUtil {
50
51 /*** */
52 protected static String BASEDIR = System.getProperty("basedir", ".");
53
54 /*** "http://www.w3.org/2001/XMLSchema" target="alexandria_uri">http://www.w3.org/2001/XMLSchema" */
55 protected static final String XSD_TYPE =
56 "http://www.w3.org/2001/XMLSchema";
57
58 /*** */
59 protected static final String SCHEMA_LANGUAGE_PROP =
60 "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
61
62 /*** */
63 protected static final String SCHEMA_LOCATION_PROP =
64 "http://apache.org/xml/properties/schema/external-schemaLocation";
65
66 /*** jdo namespace */
67 protected static final String JDO_XSD_NS =
68 "http://java.sun.com/xml/ns/jdo/jdo";
69
70 /*** orm namespace */
71 protected static final String ORM_XSD_NS =
72 "http://java.sun.com/xml/ns/jdo/orm";
73
74 /*** jdoquery namespace */
75 protected static final String JDOQUERY_XSD_NS =
76 "http://java.sun.com/xml/ns/jdo/jdoquery";
77
78 /*** jdo xsd file */
79 protected static final File JDO_XSD_FILE =
80 new File(BASEDIR + "/target/classes/javax/jdo/jdo.xsd");
81
82 /*** orm xsd file */
83 protected static final File ORM_XSD_FILE =
84 new File(BASEDIR + "/target/classes/javax/jdo/orm.xsd");
85
86 /*** jdoquery xsd file */
87 protected static final File JDOQUERY_XSD_FILE =
88 new File(BASEDIR + "/target/classes/javax/jdo/jdoquery.xsd");
89
90 /*** Entity resolver */
91 protected static final EntityResolver resolver = new JDOEntityResolver();
92
93 /*** Error handler */
94 protected static final Handler handler = new Handler();
95
96 /*** Name of the metadata property, a comma separated list of JDO metadata
97 * file or directories containing such files. */
98 protected static String METADATA_PROP = "javax.jdo.metadata";
99
100 /*** Name of the recursive property, allowing recursive search of metadata
101 * files. */
102 protected static String RECURSIVE_PROP = "javax.jdo.recursive";
103
104 /*** Separator character for the metadata property. */
105 protected static final String DELIM = ",;";
106
107 /*** Newline. */
108 protected static final String NL = System.getProperty("line.separator");
109
110 /*** XSD builder for jdo namespace. */
111 private final DocumentBuilder jdoXsdBuilder =
112 createBuilder(JDO_XSD_NS + " " + JDO_XSD_FILE.toURI().toString());
113
114 /*** XSD builder for orm namespace. */
115 private final DocumentBuilder ormXsdBuilder =
116 createBuilder(ORM_XSD_NS + " " + ORM_XSD_FILE.toURI().toString());
117
118 /*** XSD builder for jdoquery namespace. */
119 private final DocumentBuilder jdoqueryXsdBuilder =
120 createBuilder(JDOQUERY_XSD_NS + " " + JDOQUERY_XSD_FILE.toURI().toString());
121
122 /*** DTD builder. */
123 private final DocumentBuilder dtdBuilder = createBuilder(true);
124
125 /*** Non validating builder. */
126 private final DocumentBuilder nonValidatingBuilder = createBuilder(false);
127
128 /*** Create XSD builder. */
129 private DocumentBuilder createBuilder(String location) {
130 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
131 factory.setValidating(true);
132 factory.setNamespaceAware(true);
133 factory.setAttribute(SCHEMA_LANGUAGE_PROP, XSD_TYPE);
134 factory.setAttribute(SCHEMA_LOCATION_PROP, location);
135 return getParser(factory);
136 }
137
138 /*** Create builder. */
139 private DocumentBuilder createBuilder(boolean validating) {
140 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
141 factory.setValidating(validating);
142 factory.setNamespaceAware(true);
143 return getParser(factory);
144 }
145
146 /*** Returns a parser obtained from specified factroy. */
147 private DocumentBuilder getParser(DocumentBuilderFactory factory) {
148 try {
149 DocumentBuilder builder = factory.newDocumentBuilder();
150 builder.setEntityResolver(resolver);
151 builder.setErrorHandler(handler);
152 return builder;
153 } catch (ParserConfigurationException ex) {
154 throw new JDOFatalException("Cannot create XML parser", ex);
155 }
156 }
157
158 /*** Parse the specified files. The valid parameter determines whether the
159 * specified files are valid JDO metadata files. The method does not throw
160 * an exception on an error, instead it instead it returns the error
161 * message(s) as string.
162 */
163 public String checkXML(File[] files, boolean valid) {
164 StringBuffer messages = new StringBuffer();
165 for (int i = 0; i < files.length; i++) {
166 String msg = checkXML(files[i], valid);
167 if (msg != null) {
168 messages.append(msg);
169 }
170 }
171 return (messages.length() == 0) ? null : messages.toString();
172 }
173
174 /*** Parse the specified files using a non validating parser. The method
175 * does not throw an exception on an error, instead it instead it returns
176 * the error message(s) as string.
177 */
178 public String checkXMLNonValidating(File[] files) {
179 StringBuffer messages = new StringBuffer();
180 for (int i = 0; i < files.length; i++) {
181 String msg = checkXML(nonValidatingBuilder, files[i], true);
182 if (msg != null) {
183 messages.append(msg);
184 }
185 }
186 return (messages.length() == 0) ? null : messages.toString();
187 }
188
189 /*** Parse the specified file. The method checks whether it is a XSD or
190 * DTD base file and parses the file using a builder according to the file
191 * name suffix. The valid parameter determines whether the specified files
192 * are valid JDO metadata files. The method does not throw an exception on
193 * an error, instead it returns the error message(s) as string.
194 */
195 private String checkXML(File file, boolean valid) {
196 String messages = null;
197 String fileName = file.getName();
198 try {
199 if (isDTDBased(file)) {
200 messages = checkXML(dtdBuilder, file, valid);
201 } else if (fileName.endsWith(".jdo")) {
202 messages = checkXML(jdoXsdBuilder, file, valid);
203 } else if (fileName.endsWith(".orm")) {
204 messages = checkXML(ormXsdBuilder, file, valid);
205 } else if (fileName.endsWith(".jdoquery")) {
206 messages = checkXML(jdoqueryXsdBuilder, file, valid);
207 }
208 } catch (SAXException ex) {
209 messages = ex.getMessage();
210 }
211 return messages;
212 }
213
214 /*** Parse the specified file using the specified builder. The valid
215 * parameter determines whether the specified files are valid JDO metadata
216 * files. The method does not throw an exception on an error, instead it
217 * returns the error message(s) as string.
218 */
219 private String checkXML(DocumentBuilder builder, File file, boolean valid) {
220 String messages = null;
221 handler.init(file);
222 try {
223 builder.parse(file);
224 } catch (SAXParseException ex) {
225 handler.error(ex);
226 } catch (Exception ex) {
227 messages = "Fatal error processing " + file.getName() + ": " + ex;
228 }
229 if (messages == null) {
230 messages = handler.getMessages();
231 }
232 if (!valid) {
233 if (messages != null) {
234
235 messages = null;
236 } else {
237 messages = file.getName() + " is not valid, " +
238 "but the parser did not catch the error.";
239 }
240 }
241 return messages;
242 }
243
244 /*** Checks whether the specifeid file is DTD or XSD based. The method
245 * throws a SAXException if the file has syntax errors. */
246 private boolean isDTDBased(File file) throws SAXException {
247 handler.init(file);
248 try {
249 Document document = nonValidatingBuilder.parse(file);
250 return document.getDoctype() != null;
251 } catch (SAXParseException ex) {
252 handler.error(ex);
253 throw new SAXException(handler.getMessages());
254 } catch (Exception ex) {
255 throw new SAXException(
256 "Fatal error processing " + file.getName() + ": " + ex);
257 }
258 }
259
260 /*** ErrorHandler implementation. */
261 private static class Handler implements ErrorHandler {
262
263 private File fileUnderTest;
264 private String[] lines;
265 private StringBuffer messages;
266
267 public void error(SAXParseException ex) {
268 append("Handler.error: ", ex);
269 }
270
271 public void fatalError(SAXParseException ex) {
272 append("Handler.fatalError: ", ex);
273 }
274
275 public void warning(SAXParseException ex) {
276 append("Handler.warning: ", ex);
277 }
278
279 public void init(File file) {
280 this.fileUnderTest = file;
281 this.messages = new StringBuffer();
282 this.lines = null;
283 }
284
285 public String getMessages() {
286 return (messages.length() == 0) ? null : messages.toString();
287 }
288
289 private void append(String prefix, SAXParseException ex) {
290 int lineNumber = ex.getLineNumber();
291 int columnNumber = ex.getColumnNumber();
292 messages.append("------------------------").append(NL);
293 messages.append(prefix).append(fileUnderTest.getName());
294 messages.append(" [line=").append(lineNumber);
295 messages.append(", col=").append(columnNumber).append("]: ");
296 messages.append(ex.getMessage()).append(NL);
297 messages.append(getErrorLocation(lineNumber, columnNumber));
298 }
299
300 private String[] getLines() {
301 if (lines == null) {
302 try {
303 BufferedReader bufferedReader =
304 new BufferedReader(new FileReader(fileUnderTest));
305 ArrayList tmp = new ArrayList();
306 while (bufferedReader.ready()) {
307 tmp.add(bufferedReader.readLine());
308 }
309 lines = (String[])tmp.toArray(new String[tmp.size()]);
310 } catch (IOException ex) {
311 throw new JDOFatalException("getLines: caught IOException", ex);
312 }
313 }
314 return lines;
315 }
316
317 /*** Return the error location for the file under test.
318 */
319 private String getErrorLocation(int lineNumber, int columnNumber) {
320 String[] lines = getLines();
321 int length = lines.length;
322 if (lineNumber > length) {
323 return "Line number " + lineNumber +
324 " exceeds the number of lines in the file (" +
325 lines.length + ")";
326 } else if (lineNumber < 1) {
327 return "Line number " + lineNumber +
328 " does not allow retriving the error location.";
329 }
330 StringBuffer buf = new StringBuffer();
331 if (lineNumber > 2) {
332 buf.append(lines[lineNumber-3]);
333 buf.append(NL);
334 buf.append(lines[lineNumber-2]);
335 buf.append(NL);
336 }
337 buf.append(lines[lineNumber-1]);
338 buf.append(NL);
339 for (int i = 1; i < columnNumber; ++i) {
340 buf.append(' ');
341 }
342 buf.append("^\n");
343 if (lineNumber + 1 < length) {
344 buf.append(lines[lineNumber]);
345 buf.append(NL);
346 buf.append(lines[lineNumber+1]);
347 buf.append(NL);
348 }
349 return buf.toString();
350 }
351 }
352
353 /*** Implementation of EntityResolver interface to check the jdo.dtd location
354 **/
355 private static class JDOEntityResolver
356 implements EntityResolver {
357
358 private static final String RECOGNIZED_JDO_PUBLIC_ID =
359 "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 2.0//EN";
360 private static final String RECOGNIZED_JDO_SYSTEM_ID =
361 "file:/javax/jdo/jdo.dtd";
362 private static final String RECOGNIZED_JDO_SYSTEM_ID2 =
363 "http://java.sun.com/dtd/jdo_2_0.dtd";
364 private static final String RECOGNIZED_ORM_PUBLIC_ID =
365 "-//Sun Microsystems, Inc.//DTD Java Data Objects Mapping Metadata 2.0//EN";
366 private static final String RECOGNIZED_ORM_SYSTEM_ID =
367 "file:/javax/jdo/orm.dtd";
368 private static final String RECOGNIZED_ORM_SYSTEM_ID2 =
369 "http://java.sun.com/dtd/orm_2_0.dtd";
370 private static final String RECOGNIZED_JDOQUERY_PUBLIC_ID =
371 "-//Sun Microsystems, Inc.//DTD Java Data Objects Query Metadata 2.0//EN";
372 private static final String RECOGNIZED_JDOQUERY_SYSTEM_ID =
373 "file:/javax/jdo/jdoquery.dtd";
374 private static final String RECOGNIZED_JDOQUERY_SYSTEM_ID2 =
375 "http://java.sun.com/dtd/jdoquery_2_0.dtd";
376 private static final String JDO_DTD_FILENAME =
377 "javax/jdo/jdo.dtd";
378 private static final String ORM_DTD_FILENAME =
379 "javax/jdo/orm.dtd";
380 private static final String JDOQUERY_DTD_FILENAME =
381 "javax/jdo/jdoquery.dtd";
382
383 static Map publicIds = new HashMap();
384 static Map systemIds = new HashMap();
385 static {
386 publicIds.put(RECOGNIZED_JDO_PUBLIC_ID, JDO_DTD_FILENAME);
387 publicIds.put(RECOGNIZED_ORM_PUBLIC_ID, ORM_DTD_FILENAME);
388 publicIds.put(RECOGNIZED_JDOQUERY_PUBLIC_ID, JDOQUERY_DTD_FILENAME);
389 systemIds.put(RECOGNIZED_JDO_SYSTEM_ID, JDO_DTD_FILENAME);
390 systemIds.put(RECOGNIZED_ORM_SYSTEM_ID, ORM_DTD_FILENAME);
391 systemIds.put(RECOGNIZED_JDOQUERY_SYSTEM_ID, JDOQUERY_DTD_FILENAME);
392 systemIds.put(RECOGNIZED_JDO_SYSTEM_ID2, JDO_DTD_FILENAME);
393 systemIds.put(RECOGNIZED_ORM_SYSTEM_ID2, ORM_DTD_FILENAME);
394 systemIds.put(RECOGNIZED_JDOQUERY_SYSTEM_ID2, JDOQUERY_DTD_FILENAME);
395 }
396 public InputSource resolveEntity(String publicId, final String systemId)
397 throws SAXException, IOException
398 {
399
400 String filename = (String)publicIds.get(publicId);
401 if (filename == null) {
402 filename = (String)systemIds.get(systemId);
403 }
404 final String finalName = filename;
405 if (finalName == null) {
406 return null;
407 } else {
408
409
410
411
412 InputStream stream = (InputStream) AccessController.doPrivileged (
413 new PrivilegedAction () {
414 public Object run () {
415 return getClass().getClassLoader().
416 getResourceAsStream(finalName);
417 }
418 }
419 );
420 if (stream == null) {
421 throw new JDOFatalException("Cannot load " + finalName +
422 ", because the file does not exist in the jdo.jar file, " +
423 "or the JDOParser class is not granted permission to read this file. " +
424 "The metadata .xml file contained PUBLIC=" + publicId +
425 " SYSTEM=" + systemId + ".");
426 }
427 return new InputSource(new InputStreamReader(stream));
428 }
429 }
430 }
431
432 /*** Helper class to find all test JDO metadata files. */
433 public static class XMLFinder {
434
435 private List metadataFiles = new ArrayList();
436 private final boolean recursive;
437
438 /*** Constructor. */
439 public XMLFinder(String[] fileNames, boolean recursive) {
440 this.recursive = recursive;
441 if (fileNames == null) return;
442 for (int i = 0; i < fileNames.length; i++) {
443 appendTestFiles(fileNames[i]);
444 }
445 }
446
447 /*** Returns array of files of matching file names. */
448 private File[] getFiles(File dir, final String suffix) {
449 FilenameFilter filter = new FilenameFilter() {
450 public boolean accept(File file, String name) {
451 return name.endsWith(suffix);
452 }
453 };
454 return dir.listFiles(filter);
455 }
456
457 /*** */
458 private File[] getDirectories(File dir) {
459 FileFilter filter = new FileFilter() {
460 public boolean accept(File pathname) {
461 return pathname.isDirectory();
462 }
463 };
464 return dir.listFiles(filter);
465 }
466
467 /*** */
468 private void appendTestFiles(String fileName) {
469 File file = new File(fileName);
470 if (file.isDirectory()) {
471 processDirectory(file);
472 } else if (fileName.endsWith(".jdo") ||
473 fileName.endsWith(".orm") ||
474 fileName.endsWith(".jdoquery")) {
475 metadataFiles.add(new File(fileName));
476 }
477 }
478
479 /*** Adds all files with suffix .jdo, .orm and .jdoquery to the list of
480 * metadata files. Recursively process subdirectories if recursive
481 * flag is set. */
482 private void processDirectory(File dir) {
483 metadataFiles.addAll(Arrays.asList(getFiles(dir, ".jdo")));
484 metadataFiles.addAll(Arrays.asList(getFiles(dir, ".orm")));
485 metadataFiles.addAll(Arrays.asList(getFiles(dir, ".jdoquery")));
486 if (recursive) {
487 File[] subdirs = getDirectories(dir);
488 for (int i = 0; i < subdirs.length; i++) {
489 processDirectory(subdirs[i]);
490 }
491 }
492 }
493
494 /*** Returns an array of test files with suffix .jdo, .orm or .jdoquery. */
495 public File[] getMetadataFiles() {
496 return (File[])metadataFiles.toArray(new File[metadataFiles.size()]);
497 }
498
499 }
500
501 /*** */
502 private static String[] checkMetadataSystemProperty() {
503 String[] ret = null;
504 String metadata = System.getProperty(METADATA_PROP);
505 if ((metadata != null) && (metadata.length() > 0)) {
506 List entries = new ArrayList();
507 StringTokenizer st = new StringTokenizer(metadata, DELIM);
508 while (st.hasMoreTokens()) {
509 entries.add(st.nextToken());
510 }
511 ret = (String[])entries.toArray(new String[entries.size()]);
512 }
513 return ret;
514 }
515
516 /***
517 * Command line tool to test JDO metadata files.
518 * Usage: XMLTestUtil [-r] <file or directory>+
519 */
520 public static void main(String args[]) {
521 String[] fromProp = checkMetadataSystemProperty();
522 boolean recursive = Boolean.getBoolean(RECURSIVE_PROP);
523
524
525 String[] fileNames = null;
526 if ((args.length > 0) && ("-r".equals(args[0]))) {
527 recursive = true;
528 fileNames = new String[args.length - 1];
529 System.arraycopy(args, 1, fileNames, 0, args.length - 1);
530 } else {
531 fileNames = args;
532 }
533
534
535 if ((fileNames.length == 0) && (fromProp == null)) {
536 System.err.println(
537 "No commandline arguments and system property metadata not defined; " +
538 "nothing to be tested.\nUsage: XMLTestUtil [-r] <directories>\n" +
539 "\tAll .jdo, .orm, and .jdoquery files in the directory (recursively) will be tested.");
540 } else if ((fileNames.length == 0) && (fromProp != null)) {
541
542 fileNames = fromProp;
543 } else if ((fileNames.length != 0) && (fromProp != null)) {
544 System.err.println(
545 "Commandline arguments specified and system property metadata defined; " +
546 "ignoring system property metadata.");
547 }
548
549
550 XMLTestUtil xmlTest = new XMLTestUtil();
551 File[] files = new XMLFinder(fileNames, recursive).getMetadataFiles();
552 for (int i = 0; i < files.length; i++) {
553 File file = files[i];
554 System.out.print("Checking " + file.getPath() + ": ");
555 String messages = xmlTest.checkXML(file, true);
556 messages = (messages == null) ? "OK" : NL + messages;
557 System.out.println(messages);
558 }
559 }
560 }
561