From efa4da6f322d4f9686da8e736b4fb3dfce845a33 Mon Sep 17 00:00:00 2001 From: Alex Brackx Date: Thu, 3 Nov 2022 10:07:44 -0400 Subject: [PATCH] Revert "Revert Use PathHandler for writing log files (#3420)" This reverts commit 9949ca0bccd115411ad6cd45c8135d87be6d951e. --- .../commandline/CommandRunner.java | 3 +- .../commandline/LiquibaseCommandLine.java | 16 +++--- .../changelog/ChangelogRewriter.java | 13 +++-- .../changelog/visitor/DBDocVisitor.java | 3 +- .../java/liquibase/dbdoc/ChangeLogWriter.java | 3 +- .../java/liquibase/dbdoc/HTMLListWriter.java | 3 +- .../main/java/liquibase/dbdoc/HTMLWriter.java | 8 ++- .../output/changelog/DiffToChangeLog.java | 5 +- ...issingDataExternalFileChangeGenerator.java | 3 +- .../LiquibaseCommandLineConfiguration.java | 4 +- .../integration/spring/SpringResource.java | 8 ++- .../liquibase/resource/AbstractResource.java | 4 +- .../java/liquibase/resource/OpenOptions.java | 55 +++++++++++++++++++ .../resource/PathHandlerFactory.java | 18 +++++- .../java/liquibase/resource/PathResource.java | 23 +++++++- .../java/liquibase/resource/Resource.java | 15 +++-- .../liquibase/resource/ResourceAccessor.java | 4 +- .../resource/PathHandlerFactoryTest.groovy | 6 +- 18 files changed, 148 insertions(+), 46 deletions(-) create mode 100644 liquibase-core/src/main/java/liquibase/resource/OpenOptions.java diff --git a/liquibase-cli/src/main/java/liquibase/integration/commandline/CommandRunner.java b/liquibase-cli/src/main/java/liquibase/integration/commandline/CommandRunner.java index 61ee04bd360..37354f28f46 100644 --- a/liquibase-cli/src/main/java/liquibase/integration/commandline/CommandRunner.java +++ b/liquibase-cli/src/main/java/liquibase/integration/commandline/CommandRunner.java @@ -6,6 +6,7 @@ import liquibase.command.CommonArgumentNames; import liquibase.exception.CommandValidationException; import liquibase.exception.MissingRequiredArgumentException; +import liquibase.resource.OpenOptions; import liquibase.resource.PathHandlerFactory; import liquibase.util.StringUtil; import picocli.CommandLine; @@ -47,7 +48,7 @@ public CommandResults call() throws Exception { try { if (outputFile != null) { final PathHandlerFactory pathHandlerFactory = Scope.getCurrentScope().getSingleton(PathHandlerFactory.class); - outputStream = pathHandlerFactory.openResourceOutputStream(outputFile, true); + outputStream = pathHandlerFactory.openResourceOutputStream(outputFile, new OpenOptions()); commandScope.setOutput(outputStream); } diff --git a/liquibase-cli/src/main/java/liquibase/integration/commandline/LiquibaseCommandLine.java b/liquibase-cli/src/main/java/liquibase/integration/commandline/LiquibaseCommandLine.java index e93c5de131a..72020d796e6 100644 --- a/liquibase-cli/src/main/java/liquibase/integration/commandline/LiquibaseCommandLine.java +++ b/liquibase-cli/src/main/java/liquibase/integration/commandline/LiquibaseCommandLine.java @@ -60,7 +60,7 @@ public class LiquibaseCommandLine { private Level configuredLogLevel; private final CommandLine commandLine; - private FileHandler fileHandler; + private Handler fileHandler; private final ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core"); @@ -89,7 +89,6 @@ public static void main(String[] args) { private void cleanup() { if (fileHandler != null) { fileHandler.flush(); - fileHandler.close(); } } @@ -353,9 +352,9 @@ public int execute(String[] args) { int response = commandLine.execute(finalArgs); if (!wasHelpOrVersionRequested()) { - final ConfiguredValue logFile = LiquibaseCommandLineConfiguration.LOG_FILE.getCurrentConfiguredValue(); + final ConfiguredValue logFile = LiquibaseCommandLineConfiguration.LOG_FILE.getCurrentConfiguredValue(); if (logFile.found()) { - Scope.getCurrentScope().getUI().sendMessage("Logs saved to " + logFile.getValue().getAbsolutePath()); + Scope.getCurrentScope().getUI().sendMessage("Logs saved to " + logFile.getValue()); } final ConfiguredValue outputFile = LiquibaseCommandLineConfiguration.OUTPUT_FILE.getCurrentConfiguredValue(); @@ -611,7 +610,7 @@ private void configureVersionInfo() { protected Map configureLogging() throws IOException { Map returnMap = new HashMap<>(); final ConfiguredValue currentConfiguredValue = LiquibaseCommandLineConfiguration.LOG_LEVEL.getCurrentConfiguredValue(); - final File logFile = LiquibaseCommandLineConfiguration.LOG_FILE.getCurrentValue(); + final String logFile = LiquibaseCommandLineConfiguration.LOG_FILE.getCurrentValue(); Level logLevel = Level.OFF; if (!currentConfiguredValue.wasDefaultValueUsed()) { @@ -630,7 +629,7 @@ protected Map configureLogging() throws IOException { return returnMap; } - private void configureLogging(Level logLevel, File logFile) throws IOException { + private void configureLogging(Level logLevel, String logFile) throws IOException { configuredLogLevel = logLevel; final JavaLogService logService = (JavaLogService) Scope.getCurrentScope().get(Scope.Attr.logService, LogService.class); @@ -644,8 +643,9 @@ private void configureLogging(Level logLevel, File logFile) throws IOException { if (logFile != null) { if (fileHandler == null) { - fileHandler = new FileHandler(logFile.getAbsolutePath(), true); - fileHandler.setFormatter(new SimpleFormatter()); + final PathHandlerFactory pathHandlerFactory = Scope.getCurrentScope().getSingleton(PathHandlerFactory.class); + OutputStream outputStream = pathHandlerFactory.openResourceOutputStream(logFile, new OpenOptions().setAppend(true)); + fileHandler = new StreamHandler(outputStream, new SimpleFormatter()); rootLogger.addHandler(fileHandler); } diff --git a/liquibase-core/src/main/java/liquibase/changelog/ChangelogRewriter.java b/liquibase-core/src/main/java/liquibase/changelog/ChangelogRewriter.java index f5ab9b98044..18b5693dfe4 100644 --- a/liquibase-core/src/main/java/liquibase/changelog/ChangelogRewriter.java +++ b/liquibase-core/src/main/java/liquibase/changelog/ChangelogRewriter.java @@ -1,15 +1,16 @@ package liquibase.changelog; -import liquibase.Scope; import liquibase.GlobalConfiguration; +import liquibase.Scope; import liquibase.parser.core.xml.XMLChangeLogSAXParser; -import liquibase.resource.PathHandlerFactory; +import liquibase.resource.OpenOptions; import liquibase.resource.Resource; import liquibase.resource.ResourceAccessor; import liquibase.util.StreamUtil; -import java.io.*; -import java.util.SortedSet; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -73,7 +74,7 @@ public static ChangeLogRewriterResult removeChangeLogId(String changeLogFile, St } } - try (OutputStream outputStream = resource.openOutputStream(true)) { + try (OutputStream outputStream = resource.openOutputStream(new OpenOptions())) { outputStream.write(changeLogString.getBytes(encoding)); } @@ -170,7 +171,7 @@ public static ChangeLogRewriterResult addChangeLogId(String changeLogFile, Strin // // Write out the file again // - try (OutputStream outputStream = resource.openOutputStream(true)) { + try (OutputStream outputStream = resource.openOutputStream(new OpenOptions())) { outputStream.write(changeLogString.getBytes(encoding)); } diff --git a/liquibase-core/src/main/java/liquibase/changelog/visitor/DBDocVisitor.java b/liquibase-core/src/main/java/liquibase/changelog/visitor/DBDocVisitor.java index 6e819372b33..7b71b47dea5 100644 --- a/liquibase-core/src/main/java/liquibase/changelog/visitor/DBDocVisitor.java +++ b/liquibase-core/src/main/java/liquibase/changelog/visitor/DBDocVisitor.java @@ -7,6 +7,7 @@ import liquibase.database.Database; import liquibase.dbdoc.*; import liquibase.exception.LiquibaseException; +import liquibase.resource.OpenOptions; import liquibase.resource.Resource; import liquibase.resource.ResourceAccessor; import liquibase.snapshot.DatabaseSnapshot; @@ -176,7 +177,7 @@ private void copyFile(String fileToCopy, Resource rootOutputDir) throws IOExcept if (inputStream == null) { throw new IOException("Can not find " + fileToCopy); } - outputStream = rootOutputDir.resolve(fileToCopy.replaceFirst(".*\\/", "")).openOutputStream(true); + outputStream = rootOutputDir.resolve(fileToCopy.replaceFirst(".*\\/", "")).openOutputStream(new OpenOptions()); StreamUtil.copy(inputStream, outputStream); } finally { if (outputStream != null) { diff --git a/liquibase-core/src/main/java/liquibase/dbdoc/ChangeLogWriter.java b/liquibase-core/src/main/java/liquibase/dbdoc/ChangeLogWriter.java index 1cf9068f9ac..2241306080e 100644 --- a/liquibase-core/src/main/java/liquibase/dbdoc/ChangeLogWriter.java +++ b/liquibase-core/src/main/java/liquibase/dbdoc/ChangeLogWriter.java @@ -1,6 +1,7 @@ package liquibase.dbdoc; import liquibase.GlobalConfiguration; +import liquibase.resource.OpenOptions; import liquibase.resource.Resource; import liquibase.resource.ResourceAccessor; import liquibase.util.StreamUtil; @@ -20,7 +21,7 @@ public void writeChangeLog(String changeLog, String physicalFilePath) throws IOE String changeLogOutFile = changeLog.replace(":", "_"); Resource xmlFile = outputDir.resolve(changeLogOutFile.toLowerCase() + ".html"); - try (BufferedWriter changeLogStream = new BufferedWriter(new OutputStreamWriter(xmlFile.openOutputStream(true), + try (BufferedWriter changeLogStream = new BufferedWriter(new OutputStreamWriter(xmlFile.openOutputStream(new OpenOptions()), GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue()))) { Resource stylesheet = resourceAccessor.get(physicalFilePath); if (stylesheet == null) { diff --git a/liquibase-core/src/main/java/liquibase/dbdoc/HTMLListWriter.java b/liquibase-core/src/main/java/liquibase/dbdoc/HTMLListWriter.java index 9dc57135bdc..d69283efb20 100644 --- a/liquibase-core/src/main/java/liquibase/dbdoc/HTMLListWriter.java +++ b/liquibase-core/src/main/java/liquibase/dbdoc/HTMLListWriter.java @@ -1,6 +1,7 @@ package liquibase.dbdoc; import liquibase.GlobalConfiguration; +import liquibase.resource.OpenOptions; import liquibase.resource.Resource; import liquibase.util.StringUtil; @@ -21,7 +22,7 @@ public HTMLListWriter(String title, String filename, String subdir, Resource out } public void writeHTML(SortedSet objects) throws IOException { - Writer fileWriter = new OutputStreamWriter(outputDir.resolve(filename).openOutputStream(true), GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue()); + Writer fileWriter = new OutputStreamWriter(outputDir.resolve(filename).openOutputStream(new OpenOptions()), GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue()); try { fileWriter.append("\n" + "\n" + "\n"); diff --git a/liquibase-core/src/main/java/liquibase/dbdoc/HTMLWriter.java b/liquibase-core/src/main/java/liquibase/dbdoc/HTMLWriter.java index c8099ae50e0..388b21b6dff 100644 --- a/liquibase-core/src/main/java/liquibase/dbdoc/HTMLWriter.java +++ b/liquibase-core/src/main/java/liquibase/dbdoc/HTMLWriter.java @@ -2,15 +2,17 @@ import liquibase.change.Change; import liquibase.changelog.ChangeSet; -import liquibase.GlobalConfiguration; import liquibase.database.Database; import liquibase.exception.DatabaseException; import liquibase.exception.DatabaseHistoryException; +import liquibase.resource.OpenOptions; import liquibase.resource.Resource; import liquibase.util.LiquibaseUtil; import liquibase.util.StringUtil; -import java.io.*; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.text.DateFormat; import java.util.Date; import java.util.List; @@ -27,7 +29,7 @@ public HTMLWriter(Resource outputDir, Database database) { protected abstract void writeCustomHTML(Writer fileWriter, Object object, List<Change> changes, Database database) throws IOException; private Writer createFileWriter(Object object) throws IOException { - return new OutputStreamWriter(outputDir.resolve(DBDocUtil.toFileName(object.toString().toLowerCase()) + ".html").openOutputStream(true)); + return new OutputStreamWriter(outputDir.resolve(DBDocUtil.toFileName(object.toString().toLowerCase()) + ".html").openOutputStream(new OpenOptions())); } public void writeHTML(Object object, List<Change> ranChanges, List<Change> changesToRun, String changeLog) throws IOException, DatabaseHistoryException, DatabaseException { diff --git a/liquibase-core/src/main/java/liquibase/diff/output/changelog/DiffToChangeLog.java b/liquibase-core/src/main/java/liquibase/diff/output/changelog/DiffToChangeLog.java index 5d0400bbb1d..d550097d90c 100644 --- a/liquibase-core/src/main/java/liquibase/diff/output/changelog/DiffToChangeLog.java +++ b/liquibase-core/src/main/java/liquibase/diff/output/changelog/DiffToChangeLog.java @@ -16,6 +16,7 @@ import liquibase.exception.UnexpectedLiquibaseException; import liquibase.executor.Executor; import liquibase.executor.ExecutorService; +import liquibase.resource.OpenOptions; import liquibase.resource.PathHandlerFactory; import liquibase.resource.Resource; import liquibase.serializer.ChangeLogSerializer; @@ -157,7 +158,7 @@ public void run() { String toInsert = " " + innerXml + lineSeparator; fileContents.insert(endTagIndex, toInsert); } - try (OutputStream outputStream = file.openOutputStream(true)) { + try (OutputStream outputStream = file.openOutputStream(new OpenOptions())) { outputStream.write(fileContents.toString().getBytes()); } } @@ -211,7 +212,7 @@ public void printNew(ChangeLogSerializer changeLogSerializer, Resource file) thr Scope.getCurrentScope().getLog(getClass()).info(file + " does not exist, creating and adding " + changeSets.size() + " changesets."); } - try (OutputStream stream = file.openOutputStream(true); + try (OutputStream stream = file.openOutputStream(new OpenOptions()); PrintStream out = new PrintStream(stream, true, GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue())) { changeLogSerializer.write(changeSets, out); } diff --git a/liquibase-core/src/main/java/liquibase/diff/output/changelog/core/MissingDataExternalFileChangeGenerator.java b/liquibase-core/src/main/java/liquibase/diff/output/changelog/core/MissingDataExternalFileChangeGenerator.java index 30fc7b33278..48583416b90 100644 --- a/liquibase-core/src/main/java/liquibase/diff/output/changelog/core/MissingDataExternalFileChangeGenerator.java +++ b/liquibase-core/src/main/java/liquibase/diff/output/changelog/core/MissingDataExternalFileChangeGenerator.java @@ -10,6 +10,7 @@ import liquibase.diff.output.DiffOutputControl; import liquibase.diff.output.changelog.ChangeGeneratorChain; import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.resource.OpenOptions; import liquibase.resource.PathHandlerFactory; import liquibase.resource.Resource; import liquibase.servicelocator.LiquibaseService; @@ -84,7 +85,7 @@ public Change[] fixMissing(DatabaseObject missingObject, DiffOutputControl outpu String[] dataTypes = new String[0]; try ( - OutputStream fileOutputStream = externalFileResource.openOutputStream(true); + OutputStream fileOutputStream = externalFileResource.openOutputStream(new OpenOptions()); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( fileOutputStream, GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue()); CSVWriter outputFile = new CSVWriter(new BufferedWriter(outputStreamWriter)); diff --git a/liquibase-core/src/main/java/liquibase/integration/commandline/LiquibaseCommandLineConfiguration.java b/liquibase-core/src/main/java/liquibase/integration/commandline/LiquibaseCommandLineConfiguration.java index 140a77e0686..9d65a090e61 100644 --- a/liquibase-core/src/main/java/liquibase/integration/commandline/LiquibaseCommandLineConfiguration.java +++ b/liquibase-core/src/main/java/liquibase/integration/commandline/LiquibaseCommandLineConfiguration.java @@ -22,7 +22,7 @@ public class LiquibaseCommandLineConfiguration implements AutoloadedConfiguratio public static final ConfigurationDefinition<String> DEFAULTS_FILE; public static final ConfigurationDefinition<Level> LOG_LEVEL; public static final ConfigurationDefinition<String> LOG_CHANNELS; - public static final ConfigurationDefinition<File> LOG_FILE; + public static final ConfigurationDefinition<String> LOG_FILE; public static final ConfigurationDefinition<String> OUTPUT_FILE; public static final ConfigurationDefinition<Boolean> SHOULD_RUN; public static final ConfigurationDefinition<ArgumentConverter> ARGUMENT_CONVERTER; @@ -66,7 +66,7 @@ public class LiquibaseCommandLineConfiguration implements AutoloadedConfiguratio .setDefaultValue("liquibase", "Controls which log channels have their level set by the liquibase.logLevel setting. Comma separate multiple values. To set the level of all channels, use 'all'. Example: liquibase,org.mariadb.jdbc") .build(); - LOG_FILE = builder.define("logFile", File.class).build(); + LOG_FILE = builder.define("logFile", String.class).build(); OUTPUT_FILE = builder.define("outputFile", String.class).build(); MONITOR_PERFORMANCE = builder.define("monitorPerformance", String.class) diff --git a/liquibase-core/src/main/java/liquibase/integration/spring/SpringResource.java b/liquibase-core/src/main/java/liquibase/integration/spring/SpringResource.java index 466cb542fa6..cf0040228d2 100644 --- a/liquibase-core/src/main/java/liquibase/integration/spring/SpringResource.java +++ b/liquibase-core/src/main/java/liquibase/integration/spring/SpringResource.java @@ -1,6 +1,7 @@ package liquibase.integration.spring; import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.resource.OpenOptions; import org.springframework.core.io.Resource; import org.springframework.core.io.WritableResource; @@ -59,10 +60,13 @@ public InputStream openInputStream() throws IOException { } @Override - public OutputStream openOutputStream(boolean createIfNeeded) throws IOException { - if (!resource.exists() && !createIfNeeded) { + public OutputStream openOutputStream(OpenOptions openOptions) throws IOException { + if (!resource.exists() && !openOptions.isCreateIfNeeded()) { throw new IOException("Resource "+getUri()+" does not exist"); } + if (openOptions != null && openOptions.isAppend() && exists()) { + throw new IOException("Spring only supports truncating the existing resources."); + } if (resource instanceof WritableResource) { return ((WritableResource) resource).getOutputStream(); } diff --git a/liquibase-core/src/main/java/liquibase/resource/AbstractResource.java b/liquibase-core/src/main/java/liquibase/resource/AbstractResource.java index 673fdb0d905..2a62d2deed8 100644 --- a/liquibase-core/src/main/java/liquibase/resource/AbstractResource.java +++ b/liquibase-core/src/main/java/liquibase/resource/AbstractResource.java @@ -1,7 +1,5 @@ package liquibase.resource; -import liquibase.util.StringUtil; - import java.io.IOException; import java.io.OutputStream; import java.net.URI; @@ -40,7 +38,7 @@ public boolean isWritable() { } @Override - public OutputStream openOutputStream(boolean createIfNeeded) throws IOException { + public OutputStream openOutputStream(OpenOptions openOptions) throws IOException { if (!isWritable()) { throw new IOException("Read only"); } diff --git a/liquibase-core/src/main/java/liquibase/resource/OpenOptions.java b/liquibase-core/src/main/java/liquibase/resource/OpenOptions.java new file mode 100644 index 00000000000..786d02a43ca --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/resource/OpenOptions.java @@ -0,0 +1,55 @@ +package liquibase.resource; + +/** + * Defines the options for opening {@link Resource}s in Liquibase. + */ +public class OpenOptions { + private boolean truncate; + private boolean createIfNeeded; + + /** + * Use default options of truncate = true, createIfNeeded = true; + */ + public OpenOptions() { + this.truncate = true; + this.createIfNeeded = true; + } + + /** + * Should an existing file be truncated when opened. Both this and {@link #isAppend()} + * are automatically kept in sync with each other. + */ + public boolean isTruncate() { + return truncate; + } + + public OpenOptions setTruncate(boolean truncate) { + this.truncate = truncate; + return this; + } + + /** + * Should an existing file be appended to when opened. Both this and {@link #isTruncate()} + * are automatically kept in sync with each other. + */ + public boolean isAppend() { + return !isTruncate(); + } + + public OpenOptions setAppend(boolean append) { + this.truncate = !append; + return this; + } + + /** + * If true, create the resource if it does not exist. If false, do not create the resource. + */ + public boolean isCreateIfNeeded() { + return createIfNeeded; + } + + public OpenOptions setCreateIfNeeded(boolean createIfNeeded) { + this.createIfNeeded = createIfNeeded; + return this; + } +} diff --git a/liquibase-core/src/main/java/liquibase/resource/PathHandlerFactory.java b/liquibase-core/src/main/java/liquibase/resource/PathHandlerFactory.java index b4f445645ee..d78b1547f6a 100644 --- a/liquibase-core/src/main/java/liquibase/resource/PathHandlerFactory.java +++ b/liquibase-core/src/main/java/liquibase/resource/PathHandlerFactory.java @@ -1,6 +1,5 @@ package liquibase.resource; -import liquibase.Scope; import liquibase.exception.UnexpectedLiquibaseException; import liquibase.plugin.AbstractPluginFactory; @@ -75,17 +74,30 @@ public Resource getResource(String resourcePath) throws IOException { * * @return null if resourcePath does not exist and createIfNotExists is false * @throws IOException if there is an error opening the stream + * + * @deprecated use {@link #openResourceOutputStream(String, OpenOptions)} */ + @Deprecated public OutputStream openResourceOutputStream(String resourcePath, boolean createIfNotExists) throws IOException { + return openResourceOutputStream(resourcePath, new OpenOptions().setCreateIfNeeded(createIfNotExists)); + } + + /** + * Returns the outputStream from {@link #getResource(String)}, using settings from the passed {@link OpenOptions}. + * + * @return null if resourcePath does not exist and {@link OpenOptions#isCreateIfNeeded()} is false + * @throws IOException if there is an error opening the stream + */ + public OutputStream openResourceOutputStream(String resourcePath, OpenOptions openOptions) throws IOException { Resource resource = getResource(resourcePath); if (!resource.exists()) { - if (createIfNotExists) { + if (openOptions.isCreateIfNeeded()) { return createResource(resourcePath); } else { return null; } } - return resource.openOutputStream(createIfNotExists); + return resource.openOutputStream(openOptions); } /** diff --git a/liquibase-core/src/main/java/liquibase/resource/PathResource.java b/liquibase-core/src/main/java/liquibase/resource/PathResource.java index d4ebc03839d..7ea4908da70 100644 --- a/liquibase-core/src/main/java/liquibase/resource/PathResource.java +++ b/liquibase-core/src/main/java/liquibase/resource/PathResource.java @@ -4,7 +4,12 @@ import java.io.*; import java.nio.file.Files; +import java.nio.file.OpenOption; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.zip.GZIPInputStream; public class PathResource extends AbstractResource { @@ -55,9 +60,9 @@ public boolean isWritable() { } @Override - public OutputStream openOutputStream(boolean createIfNeeded) throws IOException { + public OutputStream openOutputStream(OpenOptions openOptions) throws IOException { if (!exists()) { - if (createIfNeeded) { + if (openOptions.isCreateIfNeeded()) { Path parent = path.getParent(); if (parent != null) { boolean mkdirs = parent.toFile().mkdirs(); @@ -73,7 +78,19 @@ public OutputStream openOutputStream(boolean createIfNeeded) throws IOException if (Files.isDirectory(this.path)) { throw new FileNotFoundException(this.getPath() + " is a directory"); } else { - return Files.newOutputStream(this.path); + List<StandardOpenOption> options = new ArrayList<>(); + if (openOptions.isCreateIfNeeded()) { + options.add(StandardOpenOption.CREATE); + } + + if (openOptions.isTruncate()) { + options.addAll(Arrays.asList(StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)); + } else { + options.add(StandardOpenOption.APPEND); + } + + return Files.newOutputStream(this.path, options.toArray(new OpenOption[0])); + } } } diff --git a/liquibase-core/src/main/java/liquibase/resource/Resource.java b/liquibase-core/src/main/java/liquibase/resource/Resource.java index 67323312d6d..85ec37fcbd8 100644 --- a/liquibase-core/src/main/java/liquibase/resource/Resource.java +++ b/liquibase-core/src/main/java/liquibase/resource/Resource.java @@ -50,14 +50,21 @@ public interface Resource { */ Resource resolveSibling(String other); + /** + * Opens an output stream given the passed {@link OpenOptions}. + * Cannot pass a null OpenOptions value + */ + OutputStream openOutputStream(OpenOptions openOptions) throws IOException; /** - * Opens an output stream to write to this resource. Note that calling this method will truncate (erase) the existing file. + * Opens an output stream to write to this resource using the default {@link OpenOptions#OpenOptions()} settings plus the passed createIfNeeded value. * - * @param createIfNeeded if true, create the resource if it does not exist. If false, throw an exception if it does not exist - * @throws IOException if there is an error writing to the resource, including if the resource does not exist or permission don't allow writing. + * @deprecated use {@link #openOutputStream(OpenOptions)} */ - OutputStream openOutputStream(boolean createIfNeeded) throws IOException; + @Deprecated + default OutputStream openOutputStream(boolean createIfNeeded) throws IOException { + return openOutputStream(new OpenOptions().setCreateIfNeeded(createIfNeeded)); + } /** * Returns a unique and complete identifier for this resource. diff --git a/liquibase-core/src/main/java/liquibase/resource/ResourceAccessor.java b/liquibase-core/src/main/java/liquibase/resource/ResourceAccessor.java index b86914e0677..2505b25e1a2 100644 --- a/liquibase-core/src/main/java/liquibase/resource/ResourceAccessor.java +++ b/liquibase-core/src/main/java/liquibase/resource/ResourceAccessor.java @@ -253,8 +253,8 @@ public Resource resolveSibling(String other) { } @Override - public OutputStream openOutputStream(boolean createIfNeeded) throws IOException { - throw new UnexpectedLiquibaseException("Resource does not exist"); + public OutputStream openOutputStream(OpenOptions openOptions) throws IOException { + return openOutputStream(new OpenOptions()); } } } diff --git a/liquibase-core/src/test/groovy/liquibase/resource/PathHandlerFactoryTest.groovy b/liquibase-core/src/test/groovy/liquibase/resource/PathHandlerFactoryTest.groovy index 5179c351949..55fdb7eeb69 100644 --- a/liquibase-core/src/test/groovy/liquibase/resource/PathHandlerFactoryTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/resource/PathHandlerFactoryTest.groovy @@ -52,10 +52,10 @@ class PathHandlerFactoryTest extends Specification { def pathHandlerFactory = Scope.currentScope.getSingleton(PathHandlerFactory) then: - pathHandlerFactory.openResourceOutputStream(path, false) == null //when createIfNotExists is false + pathHandlerFactory.openResourceOutputStream(path, new OpenOptions().setCreateIfNeeded(false)) == null //when createIfNotExists is false when: - def stream = pathHandlerFactory.openResourceOutputStream(path, true) //createIfNotExists is true + def stream = pathHandlerFactory.openResourceOutputStream(path, new OpenOptions().setCreateIfNeeded(true)) //createIfNotExists is true stream.withWriter { it.write("test") } @@ -66,7 +66,7 @@ class PathHandlerFactoryTest extends Specification { when: //can update file - stream = pathHandlerFactory.openResourceOutputStream(path, true) + stream = pathHandlerFactory.openResourceOutputStream(path, new OpenOptions()) stream.withWriter { it.write("test 2") }