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