| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| <<<<<<< Updated upstream |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| ======= |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| >>>>>>> Stashed changes |
| * limitations under the License. |
| */ |
| |
| package javax.jdo.util; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileReader; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.lang.reflect.InvocationTargetException; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| import javax.jdo.JDOFatalException; |
| import javax.jdo.JDOFatalInternalException; |
| import javax.jdo.LegacyJava; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import org.w3c.dom.Document; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| /** |
| * Tests schema files. |
| * |
| * <p> |
| */ |
| public class XMLTestUtil { |
| |
| /** */ |
| protected static final String BASEDIR = System.getProperty("basedir", "."); |
| |
| /** "http://www.w3.org/2001/XMLSchema" */ |
| protected static final String XSD_TYPE = "http://www.w3.org/2001/XMLSchema"; |
| |
| /** */ |
| protected static final String SCHEMA_LANGUAGE_PROP = |
| "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; |
| |
| /** */ |
| protected static final String SCHEMA_LOCATION_PROP = |
| "http://apache.org/xml/properties/schema/external-schemaLocation"; |
| |
| /** jdo namespace */ |
| protected static final String JDO_XSD_NS = "https://db.apache.org/jdo/xmlns/jdo"; |
| |
| /** orm namespace */ |
| protected static final String ORM_XSD_NS = "https://db.apache.org/jdo/xmlns/orm"; |
| |
| /** jdoquery namespace */ |
| protected static final String JDOQUERY_XSD_NS = "https://db.apache.org/jdo/xmlns/jdoquery"; |
| |
| /** jdo xsd file */ |
| protected static final File JDO_XSD_FILE = |
| new File(BASEDIR + "/target/classes/javax/jdo/jdo_3_2.xsd"); |
| |
| /** orm xsd file */ |
| protected static final File ORM_XSD_FILE = |
| new File(BASEDIR + "/target/classes/javax/jdo/orm_3_2.xsd"); |
| |
| /** jdoquery xsd file */ |
| protected static final File JDOQUERY_XSD_FILE = |
| new File(BASEDIR + "/target/classes/javax/jdo/jdoquery_3_2.xsd"); |
| |
| /** Entity resolver */ |
| protected static final EntityResolver resolver = new JDOEntityResolver(); |
| |
| /** Error handler */ |
| protected static final Handler handler = new Handler(); |
| |
| /** |
| * Name of the metadata property, a comma separated list of JDO metadata file or directories |
| * containing such files. |
| */ |
| protected static final String METADATA_PROP = "javax.jdo.metadata"; |
| |
| /** Name of the recursive property, allowing recursive search of metadata files. */ |
| protected static final String RECURSIVE_PROP = "javax.jdo.recursive"; |
| |
| /** Separator character for the metadata property. */ |
| protected static final String DELIM = ",;"; |
| |
| /** Newline. */ |
| protected static final String NL = System.getProperty("line.separator"); |
| |
| /** XSD builder for jdo namespace. */ |
| private final DocumentBuilder jdoXsdBuilder = |
| createBuilder(JDO_XSD_NS + " " + JDO_XSD_FILE.toURI().toString()); |
| |
| /** XSD builder for orm namespace. */ |
| private final DocumentBuilder ormXsdBuilder = |
| createBuilder(ORM_XSD_NS + " " + ORM_XSD_FILE.toURI().toString()); |
| |
| /** XSD builder for jdoquery namespace. */ |
| private final DocumentBuilder jdoqueryXsdBuilder = |
| createBuilder(JDOQUERY_XSD_NS + " " + JDOQUERY_XSD_FILE.toURI().toString()); |
| |
| /** DTD builder. */ |
| private final DocumentBuilder dtdBuilder = createBuilder(true); |
| |
| /** Non validating builder. */ |
| private final DocumentBuilder nonValidatingBuilder = createBuilder(false); |
| |
| /** Create XSD builder. */ |
| private DocumentBuilder createBuilder(String location) { |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| factory.setValidating(true); |
| factory.setNamespaceAware(true); |
| factory.setAttribute(SCHEMA_LANGUAGE_PROP, XSD_TYPE); |
| factory.setAttribute(SCHEMA_LOCATION_PROP, location); |
| return getParser(factory); |
| } |
| |
| /** Create builder. */ |
| private DocumentBuilder createBuilder(boolean validating) { |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| factory.setValidating(validating); |
| factory.setNamespaceAware(true); |
| return getParser(factory); |
| } |
| |
| /** Returns a parser obtained from specified factroy. */ |
| private DocumentBuilder getParser(DocumentBuilderFactory factory) { |
| try { |
| DocumentBuilder builder = factory.newDocumentBuilder(); |
| builder.setEntityResolver(resolver); |
| builder.setErrorHandler(handler); |
| return builder; |
| } catch (ParserConfigurationException ex) { |
| throw new JDOFatalException("Cannot create XML parser", ex); |
| } |
| } |
| |
| /** |
| * Parse the specified files. The valid parameter determines whether the specified files are valid |
| * JDO metadata files. The method does not throw an exception on an error, instead it instead it |
| * returns the error message(s) as string. |
| */ |
| public String checkXML(File[] files, boolean valid) { |
| StringBuffer messages = new StringBuffer(); |
| for (int i = 0; i < files.length; i++) { |
| String msg = checkXML(files[i], valid); |
| if (msg != null) { |
| messages.append(msg); |
| } |
| } |
| return (messages.length() == 0) ? null : messages.toString(); |
| } |
| |
| /** |
| * Parse the specified files using a non validating parser. The method does not throw an exception |
| * on an error, instead it instead it returns the error message(s) as string. |
| */ |
| public String checkXMLNonValidating(File[] files) { |
| StringBuffer messages = new StringBuffer(); |
| for (int i = 0; i < files.length; i++) { |
| String msg = checkXML(nonValidatingBuilder, files[i], true); |
| if (msg != null) { |
| messages.append(msg); |
| } |
| } |
| return (messages.length() == 0) ? null : messages.toString(); |
| } |
| |
| /** |
| * Parse the specified file. The method checks whether it is a XSD or DTD base file and parses the |
| * file using a builder according to the file name suffix. The valid parameter determines whether |
| * the specified files are valid JDO metadata files. The method does not throw an exception on an |
| * error, instead it returns the error message(s) as string. |
| */ |
| private String checkXML(File file, boolean valid) { |
| String messages = null; |
| String fileName = file.getName(); |
| try { |
| if (isDTDBased(file)) { |
| messages = checkXML(dtdBuilder, file, valid); |
| } else if (fileName.endsWith(".jdo")) { |
| messages = checkXML(jdoXsdBuilder, file, valid); |
| } else if (fileName.endsWith(".orm")) { |
| messages = checkXML(ormXsdBuilder, file, valid); |
| } else if (fileName.endsWith(".jdoquery")) { |
| messages = checkXML(jdoqueryXsdBuilder, file, valid); |
| } |
| } catch (SAXException ex) { |
| messages = ex.getMessage(); |
| } |
| return messages; |
| } |
| |
| /** |
| * Parse the specified file using the specified builder. The valid parameter determines whether |
| * the specified files are valid JDO metadata files. The method does not throw an exception on an |
| * error, instead it returns the error message(s) as string. |
| */ |
| private String checkXML(DocumentBuilder builder, File file, boolean valid) { |
| String messages = null; |
| handler.init(file); |
| try { |
| builder.parse(file); |
| } catch (SAXParseException ex) { |
| handler.error(ex); |
| } catch (Exception ex) { |
| messages = "Fatal error processing " + file.getName() + ": " + ex + NL; |
| } |
| if (messages == null) { |
| messages = handler.getMessages(); |
| } |
| if (!valid) { |
| if (messages != null) { |
| // expected error for negative test |
| messages = null; |
| } else { |
| messages = file.getName() + " is not valid, " + "but the parser did not catch the error."; |
| } |
| } |
| return messages; |
| } |
| |
| /** |
| * Checks whether the specifeid file is DTD or XSD based. The method throws a SAXException if the |
| * file has syntax errors. |
| */ |
| private boolean isDTDBased(File file) throws SAXException { |
| handler.init(file); |
| try { |
| Document document = nonValidatingBuilder.parse(file); |
| return document.getDoctype() != null; |
| } catch (SAXParseException ex) { |
| handler.error(ex); |
| throw new SAXException(handler.getMessages()); |
| } catch (Exception ex) { |
| throw new SAXException("Fatal error processing " + file.getName() + ": " + ex); |
| } |
| } |
| |
| /** ErrorHandler implementation. */ |
| private static class Handler implements ErrorHandler { |
| |
| private File fileUnderTest; |
| private String[] lines; |
| private StringBuffer messages; |
| |
| public void error(SAXParseException ex) { |
| append("Handler.error: ", ex); |
| } |
| |
| public void fatalError(SAXParseException ex) { |
| append("Handler.fatalError: ", ex); |
| } |
| |
| public void warning(SAXParseException ex) { |
| append("Handler.warning: ", ex); |
| } |
| |
| public void init(File file) { |
| this.fileUnderTest = file; |
| this.messages = new StringBuffer(); |
| this.lines = null; |
| } |
| |
| public String getMessages() { |
| return (messages.length() == 0) ? null : messages.toString(); |
| } |
| |
| private void append(String prefix, SAXParseException ex) { |
| int lineNumber = ex.getLineNumber(); |
| int columnNumber = ex.getColumnNumber(); |
| messages.append("------------------------").append(NL); |
| messages.append(prefix).append(fileUnderTest.getName()); |
| messages.append(" [line=").append(lineNumber); |
| messages.append(", col=").append(columnNumber).append("]: "); |
| messages.append(ex.getMessage()).append(NL); |
| messages.append(getErrorLocation(lineNumber, columnNumber)); |
| } |
| |
| private String[] getLines() { |
| if (lines == null) { |
| try { |
| BufferedReader bufferedReader = new BufferedReader(new FileReader(fileUnderTest)); |
| ArrayList<String> tmp = new ArrayList<>(); |
| while (bufferedReader.ready()) { |
| tmp.add(bufferedReader.readLine()); |
| } |
| lines = tmp.toArray(new String[tmp.size()]); |
| } catch (IOException ex) { |
| throw new JDOFatalException("getLines: caught IOException", ex); |
| } |
| } |
| return lines; |
| } |
| |
| /** Return the error location for the file under test. */ |
| private String getErrorLocation(int lineNumber, int columnNumber) { |
| String[] lines = getLines(); |
| int length = lines.length; |
| if (lineNumber > length) { |
| return "Line number " |
| + lineNumber |
| + " exceeds the number of lines in the file (" |
| + lines.length |
| + ")"; |
| } else if (lineNumber < 1) { |
| return "Line number " + lineNumber + " does not allow retriving the error location."; |
| } |
| StringBuffer buf = new StringBuffer(); |
| if (lineNumber > 2) { |
| buf.append(lines[lineNumber - 3]); |
| buf.append(NL); |
| buf.append(lines[lineNumber - 2]); |
| buf.append(NL); |
| } |
| buf.append(lines[lineNumber - 1]); |
| buf.append(NL); |
| for (int i = 1; i < columnNumber; ++i) { |
| buf.append(' '); |
| } |
| buf.append("^\n"); |
| if (lineNumber + 1 < length) { |
| buf.append(lines[lineNumber]); |
| buf.append(NL); |
| buf.append(lines[lineNumber + 1]); |
| buf.append(NL); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Implementation of EntityResolver interface to check the jdo.dtd location. TODO Somebody should |
| * update these 2.2 to 3.1 or later at some point. |
| */ |
| private static class JDOEntityResolver implements EntityResolver { |
| |
| private static final String RECOGNIZED_JDO_PUBLIC_ID = |
| "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 3.2//EN"; |
| private static final String RECOGNIZED_JDO_SYSTEM_ID = "file:/javax/jdo/jdo_3_2.dtd"; |
| private static final String RECOGNIZED_JDO_SYSTEM_ID2 = "http://xmlns.jcp.org/dtd/jdo_3_2.dtd"; |
| private static final String RECOGNIZED_ORM_PUBLIC_ID = |
| "-//Sun Microsystems, Inc.//DTD Java Data Objects Mapping Metadata 3.2//EN"; |
| private static final String RECOGNIZED_ORM_SYSTEM_ID = "file:/javax/jdo/orm_3_2.dtd"; |
| private static final String RECOGNIZED_ORM_SYSTEM_ID2 = "http://xmlns.jcp.org/dtd/orm_3_2.dtd"; |
| private static final String RECOGNIZED_JDOQUERY_PUBLIC_ID = |
| "-//Sun Microsystems, Inc.//DTD Java Data Objects Query Metadata 3.2//EN"; |
| private static final String RECOGNIZED_JDOQUERY_SYSTEM_ID = "file:/javax/jdo/jdoquery_3_2.dtd"; |
| private static final String RECOGNIZED_JDOQUERY_SYSTEM_ID2 = |
| "http://xmlns.jcp.org/dtd/jdoquery_3_2.dtd"; |
| private static final String JDO_DTD_FILENAME = "javax/jdo/jdo_3_2.dtd"; |
| private static final String ORM_DTD_FILENAME = "javax/jdo/orm_3_2.dtd"; |
| private static final String JDOQUERY_DTD_FILENAME = "javax/jdo/jdoquery_3_2.dtd"; |
| |
| static final Map<String, String> publicIds = new HashMap<>(); |
| static final Map<String, String> systemIds = new HashMap<>(); |
| |
| static { |
| publicIds.put(RECOGNIZED_JDO_PUBLIC_ID, JDO_DTD_FILENAME); |
| publicIds.put(RECOGNIZED_ORM_PUBLIC_ID, ORM_DTD_FILENAME); |
| publicIds.put(RECOGNIZED_JDOQUERY_PUBLIC_ID, JDOQUERY_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_JDO_SYSTEM_ID, JDO_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_ORM_SYSTEM_ID, ORM_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_JDOQUERY_SYSTEM_ID, JDOQUERY_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_JDO_SYSTEM_ID2, JDO_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_ORM_SYSTEM_ID2, ORM_DTD_FILENAME); |
| systemIds.put(RECOGNIZED_JDOQUERY_SYSTEM_ID2, JDOQUERY_DTD_FILENAME); |
| } |
| |
| public InputSource resolveEntity(String publicId, final String systemId) |
| throws SAXException, IOException { |
| // check for recognized ids |
| String filename = publicIds.get(publicId); |
| if (filename == null) { |
| filename = systemIds.get(systemId); |
| } |
| final String finalName = filename; |
| if (finalName == null) { |
| return null; |
| } else { |
| // Substitute the dtd with the one from javax.jdo.jdo.dtd, |
| // but only if the publicId is equal to RECOGNIZED_PUBLIC_ID |
| // or there is no publicID and the systemID is equal to |
| // RECOGNIZED_SYSTEM_ID. |
| InputStream stream = |
| doPrivileged( |
| new PrivilegedAction<InputStream>() { |
| public InputStream run() { |
| return getClass().getClassLoader().getResourceAsStream(finalName); |
| } |
| }); |
| if (stream == null) { |
| throw new JDOFatalException( |
| "Cannot load " |
| + finalName |
| + ", because the file does not exist in the jdo.jar file, " |
| + "or the JDOParser class is not granted permission to read this file. " |
| + "The metadata .xml file contained PUBLIC=" |
| + publicId |
| + " SYSTEM=" |
| + systemId |
| + "."); |
| } |
| return new InputSource(new InputStreamReader(stream)); |
| } |
| } |
| } |
| |
| /** Helper class to find all test JDO metadata files. */ |
| public static class XMLFinder { |
| |
| private final List<File> metadataFiles = new ArrayList<>(); |
| private final boolean recursive; |
| |
| /** Constructor. */ |
| public XMLFinder(String[] fileNames, boolean recursive) { |
| this.recursive = recursive; |
| if (fileNames == null) return; |
| for (int i = 0; i < fileNames.length; i++) { |
| appendTestFiles(fileNames[i]); |
| } |
| } |
| |
| /** Returns array of files of matching file names. */ |
| private File[] getFiles(File dir, final String suffix) { |
| FilenameFilter filter = (file, name) -> name.endsWith(suffix); |
| return dir.listFiles(filter); |
| } |
| |
| /** */ |
| private File[] getDirectories(File dir) { |
| FileFilter filter = File::isDirectory; |
| return dir.listFiles(filter); |
| } |
| |
| /** */ |
| private void appendTestFiles(String fileName) { |
| File file = new File(fileName); |
| if (file.isDirectory()) { |
| processDirectory(file); |
| } else if (fileName.endsWith(".jdo") |
| || fileName.endsWith(".orm") |
| || fileName.endsWith(".jdoquery")) { |
| metadataFiles.add(new File(fileName)); |
| } |
| } |
| |
| /** |
| * Adds all files with suffix .jdo, .orm and .jdoquery to the list of metadata files. |
| * Recursively process subdirectories if recursive flag is set. |
| */ |
| private void processDirectory(File dir) { |
| metadataFiles.addAll(Arrays.asList(getFiles(dir, ".jdo"))); |
| metadataFiles.addAll(Arrays.asList(getFiles(dir, ".orm"))); |
| metadataFiles.addAll(Arrays.asList(getFiles(dir, ".jdoquery"))); |
| if (recursive) { |
| File[] subdirs = getDirectories(dir); |
| for (int i = 0; i < subdirs.length; i++) { |
| processDirectory(subdirs[i]); |
| } |
| } |
| } |
| |
| /** Returns an array of test files with suffix .jdo, .orm or .jdoquery. */ |
| public File[] getMetadataFiles() { |
| return metadataFiles.toArray(new File[metadataFiles.size()]); |
| } |
| } |
| |
| /** */ |
| private static String[] checkMetadataSystemProperty() { |
| String[] ret = null; |
| String metadata = System.getProperty(METADATA_PROP); |
| if ((metadata != null) && (metadata.length() > 0)) { |
| List<String> entries = new ArrayList<>(); |
| StringTokenizer st = new StringTokenizer(metadata, DELIM); |
| while (st.hasMoreTokens()) { |
| entries.add(st.nextToken()); |
| } |
| ret = entries.toArray(new String[entries.size()]); |
| } |
| return ret; |
| } |
| |
| /** Command line tool to test JDO metadata files. Usage: XMLTestUtil [-r] <file or directory>+ */ |
| public static void main(String args[]) { |
| String[] fromProp = checkMetadataSystemProperty(); |
| boolean recursive = Boolean.getBoolean(RECURSIVE_PROP); |
| |
| // handle command line args |
| String[] fileNames = null; |
| if ((args.length > 0) && ("-r".equals(args[0]))) { |
| recursive = true; |
| fileNames = new String[args.length - 1]; |
| System.arraycopy(args, 1, fileNames, 0, args.length - 1); |
| } else { |
| fileNames = args; |
| } |
| |
| // check args |
| if ((fileNames.length == 0) && (fromProp == null)) { |
| System.err.println( |
| "No commandline arguments and system property metadata not defined; " |
| + "nothing to be tested.\nUsage: XMLTestUtil [-r] <directories>\n" |
| + "\tAll .jdo, .orm, and .jdoquery files in the directory (recursively) will be tested."); |
| } else if ((fileNames.length == 0) && (fromProp != null)) { |
| // use metadata system property |
| fileNames = fromProp; |
| } else if ((fileNames.length != 0) && (fromProp != null)) { |
| System.err.println( |
| "Commandline arguments specified and system property metadata defined; " |
| + "ignoring system property metadata."); |
| } |
| |
| // run the test |
| XMLTestUtil xmlTest = new XMLTestUtil(); |
| File[] files = new XMLFinder(fileNames, recursive).getMetadataFiles(); |
| for (int i = 0; i < files.length; i++) { |
| File file = files[i]; |
| System.out.print("Checking " + file.getPath() + ": "); |
| String messages = xmlTest.checkXML(file, true); |
| messages = (messages == null) ? "OK" : NL + messages; |
| System.out.println(messages); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static <T> T doPrivileged(PrivilegedAction<T> privilegedAction) { |
| try { |
| return (T) LegacyJava.doPrivilegedAction.invoke(null, privilegedAction); |
| } catch (IllegalAccessException | InvocationTargetException e) { |
| if (e.getCause() instanceof RuntimeException) { |
| throw (RuntimeException) e.getCause(); |
| } |
| throw new JDOFatalInternalException(e.getMessage()); |
| } |
| } |
| } |