diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java b/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java index 056de2ff9e..2c95c5b606 100644 --- a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java +++ b/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java @@ -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 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() { - @Override - public int compare(File o1, File o2) { - return o1.compareTo(o2); - } - }; - else if ("none".equals(sort)) - fileComparator = null; - else - fileComparator = FileComparator.INSTANCE; + Comparator 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"); diff --git a/jOOQ-meta/pom.xml b/jOOQ-meta/pom.xml index dabddec6f4..98ca9ca64a 100644 --- a/jOOQ-meta/pom.xml +++ b/jOOQ-meta/pom.xml @@ -48,5 +48,14 @@ org.jooq jooq + + + + + + + + + diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FileComparator.java b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java similarity index 97% rename from jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FileComparator.java rename to jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java index 23114c2396..41adafd996 100644 --- a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FileComparator.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java @@ -35,7 +35,7 @@ * * */ -package org.jooq.meta.extensions.tools; +package org.jooq.meta.tools; import java.io.File; import java.util.Comparator; diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java new file mode 100644 index 0000000000..9c6d5c3605 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java @@ -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. + *

+ * 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 fileComparator(String sort) { + if ("alphanumeric".equals(sort)) + return new Comparator() { + @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 pattern. + *

+ * This method tries loading contents from pattern using the + * following algorithm: + *

+ *

    + *
  • If pattern is a valid classpath resource, load it from + * there
  • + *
  • If pattern is a valid file on the file system, load it + * from there
  • + *
  • Match all files on the file system according to pattern + * (interpreted as an ant-style file pattern), and load them
  • + *
+ */ + public static final void load( + String encoding, + String pattern, + Comparator 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 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; + } +} diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FilenameComparator.java b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java similarity index 98% rename from jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FilenameComparator.java rename to jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java index f27a5e0ae5..400091a056 100644 --- a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/tools/FilenameComparator.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java @@ -35,7 +35,7 @@ * * */ -package org.jooq.meta.extensions.tools; +package org.jooq.meta.tools; import java.math.BigInteger; import java.util.Comparator; diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java index ac33a200f4..9052e076c5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java @@ -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( + "", + ""); + + // [#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( - "", - ""); - - // [#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);