BaseCommand.java

  1. /*
  2.  *    Copyright 2010-2023 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.migration.commands;

  17. import static org.apache.ibatis.migration.utils.Util.file;

  18. import java.io.File;
  19. import java.io.FileReader;
  20. import java.io.FileWriter;
  21. import java.io.IOException;
  22. import java.io.LineNumberReader;
  23. import java.io.OutputStream;
  24. import java.io.PrintStream;
  25. import java.io.PrintWriter;
  26. import java.io.Reader;
  27. import java.net.URL;
  28. import java.net.URLClassLoader;
  29. import java.text.DecimalFormat;
  30. import java.text.ParseException;
  31. import java.text.SimpleDateFormat;
  32. import java.util.ArrayList;
  33. import java.util.Date;
  34. import java.util.List;
  35. import java.util.Properties;
  36. import java.util.ServiceLoader;
  37. import java.util.TimeZone;

  38. import org.apache.ibatis.migration.Change;
  39. import org.apache.ibatis.migration.ConnectionProvider;
  40. import org.apache.ibatis.migration.Environment;
  41. import org.apache.ibatis.migration.FileMigrationLoader;
  42. import org.apache.ibatis.migration.FileMigrationLoaderFactory;
  43. import org.apache.ibatis.migration.JdbcConnectionProvider;
  44. import org.apache.ibatis.migration.MigrationException;
  45. import org.apache.ibatis.migration.MigrationLoader;
  46. import org.apache.ibatis.migration.VariableReplacer;
  47. import org.apache.ibatis.migration.hook.FileHookScriptFactory;
  48. import org.apache.ibatis.migration.hook.FileMigrationHook;
  49. import org.apache.ibatis.migration.hook.HookScriptFactory;
  50. import org.apache.ibatis.migration.hook.MigrationHook;
  51. import org.apache.ibatis.migration.io.Resources;
  52. import org.apache.ibatis.migration.options.DatabaseOperationOption;
  53. import org.apache.ibatis.migration.options.Options;
  54. import org.apache.ibatis.migration.options.SelectedOptions;
  55. import org.apache.ibatis.migration.options.SelectedPaths;
  56. import org.apache.ibatis.migration.utils.Util;

  57. public abstract class BaseCommand implements Command {
  58.   private static final String DATE_FORMAT = "yyyyMMddHHmmss";
  59.   protected static final String DESC_CREATE_CHANGELOG = "create changelog";

  60.   private ClassLoader driverClassLoader;
  61.   private Environment environment;

  62.   protected PrintStream printStream = System.out;

  63.   protected final SelectedOptions options;
  64.   protected final SelectedPaths paths;

  65.   protected BaseCommand(SelectedOptions selectedOptions) {
  66.     this.options = selectedOptions;
  67.     this.paths = selectedOptions.getPaths();
  68.     if (options.isQuiet()) {
  69.       this.printStream = new PrintStream(new OutputStream() {
  70.         @Override
  71.         public void write(int b) {
  72.           // throw away output
  73.         }
  74.       });
  75.     }
  76.   }

  77.   public void setDriverClassLoader(ClassLoader aDriverClassLoader) {
  78.     driverClassLoader = aDriverClassLoader;
  79.   }

  80.   public void setPrintStream(PrintStream aPrintStream) {
  81.     if (options.isQuiet()) {
  82.       aPrintStream.println("You selected to suppress output but a PrintStream is being set");
  83.     }
  84.     printStream = aPrintStream;
  85.   }

  86.   protected boolean paramsEmpty(String... params) {
  87.     return params == null || params.length < 1 || params[0] == null || params[0].length() < 1;
  88.   }

  89.   protected String changelogTable() {
  90.     return environment().getVariables().getProperty(Environment.CHANGELOG, "CHANGELOG");
  91.   }

  92.   protected String getNextIDAsString() {
  93.     try {
  94.       // Ensure that two subsequent calls are less likely to return the same value.
  95.       Thread.sleep(1000);
  96.     } catch (InterruptedException e) {
  97.       // Ignore and Restore interrupted state...
  98.       Thread.currentThread().interrupt();
  99.     }
  100.     String idPattern = options.getIdPattern();
  101.     if (idPattern == null) {
  102.       idPattern = Util.getPropertyOption(Options.IDPATTERN.toString().toLowerCase());
  103.     }
  104.     if (idPattern != null && !idPattern.isEmpty()) {
  105.       return generatePatternedId(idPattern);
  106.     }
  107.     return generateTimestampId();
  108.   }

  109.   private String generatePatternedId(String pattern) {
  110.     DecimalFormat fmt = new DecimalFormat(pattern);
  111.     List<Change> migrations = getMigrationLoader().getMigrations();
  112.     if (migrations.isEmpty()) {
  113.       return fmt.format(1);
  114.     }
  115.     Change lastChange = migrations.get(migrations.size() - 1);
  116.     try {
  117.       long lastId = (Long) fmt.parse(lastChange.getId().toString());
  118.       lastId++;
  119.       return fmt.format(lastId);
  120.     } catch (ParseException e) {
  121.       throw new MigrationException(
  122.           "Failed to parse last id '" + lastChange.getId() + "' using the specified idPattern '" + pattern + "'");
  123.     }
  124.   }

  125.   private String generateTimestampId() {
  126.     final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
  127.     final Date now = new Date();
  128.     dateFormat.setTimeZone(TimeZone.getTimeZone(environment().getTimeZone()));
  129.     return dateFormat.format(now);
  130.   }

  131.   protected void copyResourceTo(String resource, File toFile) {
  132.     copyResourceTo(resource, toFile, null);
  133.   }

  134.   protected void copyResourceTo(String resource, File toFile, Properties variables) {
  135.     printStream.println("Creating: " + toFile.getName());
  136.     try (Reader reader = Resources.getResourceAsReader(this.getClass().getClassLoader(), resource)) {
  137.       copyTemplate(reader, toFile, variables);
  138.     } catch (IOException e) {
  139.       throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e,
  140.           e);
  141.     }
  142.   }

  143.   protected void copyExternalResourceTo(String resource, File toFile, Properties variables) {
  144.     printStream.println("Creating: " + toFile.getName());
  145.     try {
  146.       File sourceFile = new File(resource);
  147.       copyTemplate(sourceFile, toFile, variables);
  148.     } catch (Exception e) {
  149.       throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e,
  150.           e);
  151.     }
  152.   }

  153.   protected static void copyTemplate(File templateFile, File toFile, Properties variables) throws IOException {
  154.     try (FileReader reader = new FileReader(templateFile)) {
  155.       copyTemplate(reader, toFile, variables);
  156.     }
  157.   }

  158.   protected static void copyTemplate(Reader templateReader, File toFile, Properties variables) throws IOException {
  159.     VariableReplacer replacer = new VariableReplacer(variables);
  160.     try (LineNumberReader reader = new LineNumberReader(templateReader);
  161.         PrintWriter writer = new PrintWriter(new FileWriter(toFile))) {
  162.       String line;
  163.       while ((line = reader.readLine()) != null) {
  164.         line = replacer.replace(line);
  165.         writer.println(line);
  166.       }
  167.     }
  168.   }

  169.   protected File environmentFile() {
  170.     return file(paths.getEnvPath(), options.getEnvironment() + ".properties");
  171.   }

  172.   protected File existingEnvironmentFile() {
  173.     File envFile = environmentFile();
  174.     if (!envFile.exists()) {
  175.       throw new MigrationException("Environment file missing: " + envFile.getAbsolutePath());
  176.     }
  177.     return envFile;
  178.   }

  179.   protected Environment environment() {
  180.     if (environment != null) {
  181.       return environment;
  182.     }
  183.     environment = new Environment(existingEnvironmentFile());
  184.     return environment;
  185.   }

  186.   protected int getStepCountParameter(int defaultSteps, String... params) {
  187.     final String stringParam = params.length > 0 ? params[0] : null;
  188.     if (stringParam == null || "".equals(stringParam)) {
  189.       return defaultSteps;
  190.     }
  191.     try {
  192.       return Integer.parseInt(stringParam);
  193.     } catch (NumberFormatException e) {
  194.       throw new MigrationException("Invalid parameter passed to command: " + params[0]);
  195.     }
  196.   }

  197.   protected ConnectionProvider getConnectionProvider() {
  198.     try {
  199.       return new JdbcConnectionProvider(getDriverClassLoader(), environment().getDriver(), environment().getUrl(),
  200.           environment().getUsername(), environment().getPassword());
  201.     } catch (Exception e) {
  202.       throw new MigrationException("Error creating ScriptRunner.  Cause: " + e, e);
  203.     }
  204.   }

  205.   private ClassLoader getDriverClassLoader() {
  206.     File localDriverPath = getCustomDriverPath();
  207.     if (driverClassLoader != null) {
  208.       return driverClassLoader;
  209.     }
  210.     if (localDriverPath.exists()) {
  211.       try {
  212.         List<URL> urlList = new ArrayList<>();
  213.         File[] files = localDriverPath.listFiles();
  214.         if (files != null) {
  215.           for (File file : files) {
  216.             String filename = file.getCanonicalPath();
  217.             if (!filename.startsWith("/")) {
  218.               filename = '/' + filename;
  219.             }
  220.             urlList.add(new URL("jar:file:" + filename + "!/"));
  221.             urlList.add(new URL("file:" + filename));
  222.           }
  223.         }
  224.         URL[] urls = urlList.toArray(new URL[0]);
  225.         return new URLClassLoader(urls);
  226.       } catch (Exception e) {
  227.         throw new MigrationException("Error creating a driver ClassLoader. Cause: " + e, e);
  228.       }
  229.     }
  230.     return null;
  231.   }

  232.   private File getCustomDriverPath() {
  233.     String customDriverPath = environment().getDriverPath();
  234.     if (customDriverPath != null && customDriverPath.length() > 0) {
  235.       return new File(customDriverPath);
  236.     }
  237.     return options.getPaths().getDriverPath();
  238.   }

  239.   protected MigrationLoader getMigrationLoader() {
  240.     Environment env = environment();
  241.     MigrationLoader migrationLoader = null;
  242.     for (FileMigrationLoaderFactory factory : ServiceLoader.load(FileMigrationLoaderFactory.class)) {
  243.       if (migrationLoader != null) {
  244.         throw new MigrationException("Found multiple implementations of FileMigrationLoaderFactory via SPI.");
  245.       }
  246.       migrationLoader = factory.create(paths, env);
  247.     }
  248.     return migrationLoader != null ? migrationLoader
  249.         : new FileMigrationLoader(paths.getScriptPath(), env.getScriptCharset(), env.getVariables());
  250.   }

  251.   protected MigrationHook createUpHook() {
  252.     String before = environment().getHookBeforeUp();
  253.     String beforeEach = environment().getHookBeforeEachUp();
  254.     String afterEach = environment().getHookAfterEachUp();
  255.     String after = environment().getHookAfterUp();
  256.     if (before == null && beforeEach == null && afterEach == null && after == null) {
  257.       return null;
  258.     }
  259.     return createFileMigrationHook(before, beforeEach, afterEach, after);
  260.   }

  261.   protected MigrationHook createDownHook() {
  262.     String before = environment().getHookBeforeDown();
  263.     String beforeEach = environment().getHookBeforeEachDown();
  264.     String afterEach = environment().getHookAfterEachDown();
  265.     String after = environment().getHookAfterDown();
  266.     if (before == null && beforeEach == null && afterEach == null && after == null) {
  267.       return null;
  268.     }
  269.     return createFileMigrationHook(before, beforeEach, afterEach, after);
  270.   }

  271.   protected MigrationHook createFileMigrationHook(String before, String beforeEach, String afterEach, String after) {
  272.     HookScriptFactory factory = new FileHookScriptFactory(options.getPaths(), environment(), printStream);
  273.     return new FileMigrationHook(factory.create(before), factory.create(beforeEach), factory.create(afterEach),
  274.         factory.create(after));
  275.   }

  276.   protected DatabaseOperationOption getDatabaseOperationOption() {
  277.     DatabaseOperationOption option = new DatabaseOperationOption();
  278.     option.setChangelogTable(changelogTable());
  279.     option.setStopOnError(!options.isForce());
  280.     option.setThrowWarning(!options.isForce() && !environment().isIgnoreWarnings());
  281.     option.setEscapeProcessing(false);
  282.     option.setAutoCommit(environment().isAutoCommit());
  283.     option.setFullLineDelimiter(environment().isFullLineDelimiter());
  284.     option.setSendFullScript(environment().isSendFullScript());
  285.     option.setRemoveCRs(environment().isRemoveCrs());
  286.     option.setDelimiter(environment().getDelimiter());
  287.     return option;
  288.   }
  289. }