[#6260] Support loading multiple files in XMLDatabase

This commit is contained in:
lukaseder 2018-12-14 12:52:15 +01:00
parent 89f79d5f47
commit df54d660f4
6 changed files with 294 additions and 180 deletions

View File

@ -42,13 +42,10 @@ import static org.jooq.impl.DSL.name;
import static org.jooq.tools.StringUtils.isBlank;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@ -69,8 +66,9 @@ import org.jooq.impl.DSL;
import org.jooq.impl.DefaultVisitListener;
import org.jooq.impl.ParserException;
import org.jooq.meta.SchemaDefinition;
import org.jooq.meta.extensions.tools.FileComparator;
import org.jooq.meta.h2.H2Database;
import org.jooq.meta.tools.FilePattern;
import org.jooq.meta.tools.FilePattern.Loader;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.jdbc.JDBCUtils;
@ -95,7 +93,6 @@ public class DDLDatabase extends H2Database {
private Connection connection;
private DSLContext ctx;
private Comparator<File> fileComparator;
private boolean publicIsDefault;
@Override
@ -108,18 +105,7 @@ public class DDLDatabase extends H2Database {
String defaultNameCase = getProperties().getProperty("defaultNameCase", "as_is").toUpperCase();
publicIsDefault = "none".equals(unqualifiedSchema);
if ("alphanumeric".equals(sort))
fileComparator = new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.compareTo(o2);
}
};
else if ("none".equals(sort))
fileComparator = null;
else
fileComparator = FileComparator.INSTANCE;
Comparator<File> fileComparator = FilePattern.fileComparator(sort);
if (isBlank(scripts)) {
scripts = "";
@ -162,41 +148,12 @@ public class DDLDatabase extends H2Database {
});
}
InputStream in = null;
boolean loaded = false;
in = DDLDatabase.class.getResourceAsStream(scripts);
if (in != null) {
log.info("Reading from classpath: " + scripts);
load(encoding, in);
loaded = true;
}
else {
File file = new File(scripts);
if (file.exists()) {
load(encoding, file, null);
loaded = true;
FilePattern.load(encoding, scripts, fileComparator, new Loader() {
@Override
public void load(String e, InputStream in) {
DDLDatabase.this.load(e, in);
}
else if (scripts.contains("*") || scripts.contains("?")) {
file = new File(scripts.replaceAll("[*?].*", ""));
Pattern pattern = Pattern.compile("^.*?"
+ scripts
.replace("\\", "/")
.replace(".", "\\.")
.replace("?", ".")
.replace("**", ".+?")
.replace("*", "[^/]*")
+ "$"
);
load(encoding, file, pattern);
loaded = true;
}
}
if (!loaded)
log.error("Could not find script source : " + scripts);
});
}
catch (ParserException e) {
log.error("An exception occurred while parsing script source : " + scripts + ". Please report this error to https://github.com/jOOQ/jOOQ/issues/new", e);
@ -210,33 +167,6 @@ public class DDLDatabase extends H2Database {
return DSL.using(connection);
}
private void load(String encoding, File file, Pattern pattern) throws IOException {
if (file.isFile()) {
if (pattern == null || pattern.matcher(file.getCanonicalPath().replace("\\", "/")).matches()) {
log.info("Reading from: " + file + " [*]");
load(encoding, new FileInputStream(file));
}
}
else if (file.isDirectory()) {
log.info("Reading from: " + file);
File[] files = file.listFiles();
if (files != null) {
if (fileComparator != null)
Arrays.sort(files, fileComparator);
for (File f : files)
load(encoding, f, pattern);
}
}
// [#7767] Backtrack to a parent directory in case the current file pattern doesn't match yet
else if (!file.exists() && file.getParentFile() != null) {
load(encoding, file.getParentFile(), pattern);
}
}
private void load(String encoding, InputStream in) {
try {
Scanner s = new Scanner(in, encoding).useDelimiter("\\A");

View File

@ -48,5 +48,14 @@
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -35,7 +35,7 @@
*
*
*/
package org.jooq.meta.extensions.tools;
package org.jooq.meta.tools;
import java.io.File;
import java.util.Comparator;

View File

@ -0,0 +1,181 @@
/*
* Licensed 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
*
* 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
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.meta.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;
import org.jooq.tools.JooqLogger;
/**
* A utility class that can traverse a directory structure given some ant-style
* file patterns.
* <p>
* This is INTERNAL API. Please do not use directly as API may change
* incompatibly.
*
* @author Lukas Eder
*/
public final class FilePattern {
private static final JooqLogger log = JooqLogger.getLogger(FilePattern.class);
public static final Comparator<File> fileComparator(String sort) {
if ("alphanumeric".equals(sort))
return new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.compareTo(o2);
}
};
else if ("none".equals(sort))
return null;
else
return FileComparator.INSTANCE;
}
/**
* Try loading <code>pattern</code>.
* <p>
* This method tries loading contents from <code>pattern</code> using the
* following algorithm:
* <p>
* <ul>
* <li>If <code>pattern</code> is a valid classpath resource, load it from
* there</li>
* <li>If <code>pattern</code> is a valid file on the file system, load it
* from there</li>
* <li>Match all files on the file system according to <code>pattern</code>
* (interpreted as an ant-style file pattern), and load them</li>
* </ul>
*/
public static final void load(
String encoding,
String pattern,
Comparator<File> fileComparator,
Loader loader
) throws Exception {
InputStream in = null;
boolean loaded = false;
try {
in = FilePattern.class.getResourceAsStream(pattern);
if (in != null) {
log.info("Reading from classpath: " + pattern);
loader.load(encoding, in);
loaded = true;
}
else {
File file = new File(pattern);
if (file.exists()) {
load(encoding, file, fileComparator, null, loader);
loaded = true;
}
else if (pattern.contains("*") || pattern.contains("?")) {
file = new File(pattern.replaceAll("[*?].*", "")).getCanonicalFile();
Pattern regex = Pattern.compile("^.*?"
+ pattern
.replace("\\", "/")
.replace(".", "\\.")
.replace("?", ".")
.replace("**", ".+?")
.replace("*", "[^/]*")
+ "$"
);
load(encoding, file, fileComparator, regex, loader);
loaded = true;
}
}
if (!loaded)
log.error("Could not find source(s) : " + pattern);
}
finally {
try {
if (in != null)
in.close();
}
catch (Exception ignore) {}
}
}
private static final void load(
String encoding,
File file,
Comparator<File> fileComparator,
Pattern pattern,
Loader loader
) throws Exception {
if (file.isFile()) {
if (pattern == null || pattern.matcher(file.getCanonicalPath().replace("\\", "/")).matches()) {
log.info("Reading from: " + file + " [*]");
loader.load(encoding, new FileInputStream(file));
}
}
else if (file.isDirectory()) {
log.info("Reading from: " + file);
File[] files = file.listFiles();
if (files != null) {
if (fileComparator != null)
Arrays.sort(files, fileComparator);
for (File f : files)
load(encoding, f, fileComparator, pattern, loader);
}
}
// [#7767] Backtrack to a parent directory in case the current file pattern doesn't match yet
else if (!file.exists() && file.getParentFile() != null) {
load(encoding, file.getParentFile(), fileComparator, pattern, loader);
}
}
public interface Loader {
void load(String encoding, InputStream in) throws Exception;
}
}

View File

@ -35,7 +35,7 @@
*
*
*/
package org.jooq.meta.extensions.tools;
package org.jooq.meta.tools;
import java.math.BigInteger;
import java.util.Comparator;

View File

@ -45,14 +45,13 @@ import static org.jooq.tools.StringUtils.isBlank;
import static org.jooq.util.xml.jaxb.TableConstraintType.PRIMARY_KEY;
import static org.jooq.util.xml.jaxb.TableConstraintType.UNIQUE;
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
@ -103,6 +102,8 @@ import org.jooq.meta.SchemaDefinition;
import org.jooq.meta.SequenceDefinition;
import org.jooq.meta.TableDefinition;
import org.jooq.meta.UDTDefinition;
import org.jooq.meta.tools.FilePattern;
import org.jooq.meta.tools.FilePattern.Loader;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.util.xml.jaxb.Index;
@ -132,122 +133,115 @@ public class XMLDatabase extends AbstractDatabase {
if (info == null) {
// [#8115] Support old property name style for backwards compatibility reasons
String xml = getProperties().getProperty("xmlFile", getProperties().getProperty("xml-file"));
String xsl = getProperties().getProperty("xslFile", getProperties().getProperty("xsl-file"));
final String xml = getProperties().getProperty("xmlFiles",
getProperties().getProperty("xmlFile",
getProperties().getProperty("xml-file")
)
);
final String xsl = getProperties().getProperty("xslFile",
getProperties().getProperty("xsl-file")
);
final String sort = getProperties().getProperty("sort", "semantic").toLowerCase();
if (xml == null)
throw new RuntimeException("Must provide an xmlFile property");
log.info("Using XML file", xml);
try {
String content;
FilePattern.load("UTF-8", xml, FilePattern.fileComparator(sort), new Loader() {
@Override
public void load(String enc, InputStream in) throws Exception {
String content;
if (StringUtils.isBlank(xsl)) {
RandomAccessFile f = null;
if (StringUtils.isBlank(xsl)) {
byte[] bytes = bytes(in);
try {
URL url = XMLDatabase.class.getResource(xml);
File file = url != null ? new File(url.toURI()) : new File(xml);
// [#7414] Default to reading UTF-8
content = new String(bytes, "UTF-8");
f = new RandomAccessFile(file, "r");
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
// [#7414] Alternatively, read the encoding from the XML file
try {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(content));
String encoding = reader.getCharacterEncodingScheme();
// [#7414] Default to reading UTF-8
content = new String(bytes, "UTF-8");
// Returned encoding can be null in the presence of a BOM
// See https://stackoverflow.com/a/27147259/521799
if (encoding != null && !"UTF-8".equals(encoding))
content = new String(bytes, encoding);
}
catch (XMLStreamException e) {
log.warn("Could not open XML Stream: " + e.getMessage());
}
catch (UnsupportedEncodingException e) {
log.warn("Unsupported encoding: " + e.getMessage());
}
}
else {
InputStream xslIs = null;
try {
log.info("Using XSL file", xsl);
xslIs = XMLDatabase.class.getResourceAsStream(xsl);
if (xslIs == null)
xslIs = new FileInputStream(xsl);
StringWriter writer = new StringWriter();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(xslIs));
transformer.transform(new StreamSource(in), new StreamResult(writer));
content = writer.getBuffer().toString();
}
catch (TransformerException e) {
throw new RuntimeException("Error while transforming XML file " + xml + " with XSL file " + xsl, e);
}
finally {
if (xslIs != null) {
try {
xslIs.close();
}
catch (Exception ignore) {}
}
}
}
// TODO [#1201] Add better error handling here
content = content.replaceAll(
"<(\\w+:)?information_schema xmlns(:\\w+)?=\"http://www.jooq.org/xsd/jooq-meta-\\d+\\.\\d+\\.\\d+.xsd\">",
"<$1information_schema xmlns$2=\"" + Constants.NS_META + "\">");
content = content.replace(
"<information_schema>",
"<information_schema xmlns=\"" + Constants.NS_META + "\">");
// [#7579] [#8044] Workaround for obscure JAXB bug on JDK 9+
content = MiniJAXB.jaxbNamespaceBugWorkaround(content, new InformationSchema());
// [#7414] Alternatively, read the encoding from the XML file
try {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader( new StringReader(content) );
String encoding = reader.getCharacterEncodingScheme();
info = MiniJAXB.append(info, JAXB.unmarshal(new StringReader(content), InformationSchema.class));
}
catch (Throwable t) {
if (ExceptionTools.getCause(t, ClassNotFoundException.class) != null ||
ExceptionTools.getCause(t, Error.class) != null) {
// Returned encoding can be null in the presence of a BOM
// See https://stackoverflow.com/a/27147259/521799
if (encoding != null && !"UTF-8".equals(encoding))
content = new String(bytes, encoding);
}
catch (XMLStreamException e) {
log.warn("Could not open XML Stream: " + e.getMessage());
}
catch (UnsupportedEncodingException e) {
log.warn("Unsupported encoding: " + e.getMessage());
}
}
finally {
if (f != null) {
try {
f.close();
info = MiniJAXB.append(info, MiniJAXB.unmarshal(content, InformationSchema.class));
}
catch (Exception ignore) {}
else
throw t;
}
}
}
else {
InputStream xmlIs = null;
InputStream xslIs = null;
try {
log.info("Using XSL file", xsl);
private byte[] bytes(InputStream in) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read;
byte[] buffer = new byte[16384];
xmlIs = XMLDatabase.class.getResourceAsStream(xml);
if (xmlIs == null)
xmlIs = new FileInputStream(xml);
while ((read = in.read(buffer, 0, buffer.length)) != -1)
bos.write(buffer, 0, read);
xslIs = XMLDatabase.class.getResourceAsStream(xsl);
if (xslIs == null)
xslIs = new FileInputStream(xsl);
StringWriter writer = new StringWriter();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(xslIs));
transformer.transform(new StreamSource(xmlIs), new StreamResult(writer));
content = writer.getBuffer().toString();
return bos.toByteArray();
}
catch (TransformerException e) {
throw new RuntimeException("Error while transforming XML file " + xml + " with XSL file " + xsl, e);
}
finally {
if (xmlIs != null) {
try {
xmlIs.close();
}
catch (Exception ignore) {}
}
if (xslIs != null) {
try {
xslIs.close();
}
catch (Exception ignore) {}
}
}
}
// TODO [#1201] Add better error handling here
content = content.replaceAll(
"<(\\w+:)?information_schema xmlns(:\\w+)?=\"http://www.jooq.org/xsd/jooq-meta-\\d+\\.\\d+\\.\\d+.xsd\">",
"<$1information_schema xmlns$2=\"" + Constants.NS_META + "\">");
content = content.replace(
"<information_schema>",
"<information_schema xmlns=\"" + Constants.NS_META + "\">");
// [#7579] [#8044] Workaround for obscure JAXB bug on JDK 9+
content = MiniJAXB.jaxbNamespaceBugWorkaround(content, new InformationSchema());
try {
info = JAXB.unmarshal(new StringReader(content), InformationSchema.class);
}
catch (Throwable t) {
if (ExceptionTools.getCause(t, ClassNotFoundException.class) != null ||
ExceptionTools.getCause(t, Error.class) != null) {
info = MiniJAXB.unmarshal(content, InformationSchema.class);
}
else
throw t;
}
});
}
catch (Exception e) {
throw new RuntimeException("Error while opening files " + xml + " or " + xsl, e);