• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / migrations / 718

23 May 2025 05:07PM UTC coverage: 80.917% (+0.04%) from 80.873%
718

push

github

web-flow
Merge pull request #448 from harawata/use-nio

Use NIO to make modernizer happy

692 of 986 branches covered (70.18%)

20 of 28 new or added lines in 11 files covered. (71.43%)

1836 of 2269 relevant lines covered (80.92%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.1
/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java
1
/*
2
 *    Copyright 2010-2025 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

18
import static org.apache.ibatis.migration.utils.Util.file;
19

20
import java.io.File;
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.nio.file.Files;
30
import java.nio.file.Path;
31
import java.text.DecimalFormat;
32
import java.text.ParseException;
33
import java.text.SimpleDateFormat;
34
import java.util.ArrayList;
35
import java.util.Date;
36
import java.util.List;
37
import java.util.Properties;
38
import java.util.ServiceLoader;
39
import java.util.TimeZone;
40

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

61
public abstract class BaseCommand implements Command {
62
  private static final String DATE_FORMAT = "yyyyMMddHHmmss";
63
  protected static final String DESC_CREATE_CHANGELOG = "create changelog";
64

65
  private ClassLoader driverClassLoader;
66
  private Environment environment;
67

68
  protected PrintStream printStream = System.out;
1✔
69

70
  protected final SelectedOptions options;
71
  protected final SelectedPaths paths;
72

73
  protected BaseCommand(SelectedOptions selectedOptions) {
1✔
74
    this.options = selectedOptions;
1✔
75
    this.paths = selectedOptions.getPaths();
1✔
76
    if (options.isQuiet()) {
1✔
77
      this.printStream = new PrintStream(new OutputStream() {
1✔
78
        @Override
79
        public void write(int b) {
80
          // throw away output
81
        }
1✔
82
      });
83
    }
84
  }
1✔
85

86
  public void setDriverClassLoader(ClassLoader aDriverClassLoader) {
87
    driverClassLoader = aDriverClassLoader;
×
88
  }
×
89

90
  public void setPrintStream(PrintStream aPrintStream) {
91
    if (options.isQuiet()) {
×
92
      aPrintStream.println("You selected to suppress output but a PrintStream is being set");
×
93
    }
94
    printStream = aPrintStream;
×
95
  }
×
96

97
  protected boolean paramsEmpty(String... params) {
98
    return params == null || params.length < 1 || params[0] == null || params[0].length() < 1;
1!
99
  }
100

101
  protected String changelogTable() {
102
    return environment().getVariables().getProperty(Environment.CHANGELOG, "CHANGELOG");
1✔
103
  }
104

105
  protected String getNextIDAsString() {
106
    try {
107
      // Ensure that two subsequent calls are less likely to return the same value.
108
      Thread.sleep(1000);
1✔
109
    } catch (InterruptedException e) {
×
110
      // Ignore and Restore interrupted state...
111
      Thread.currentThread().interrupt();
×
112
    }
1✔
113
    String idPattern = options.getIdPattern();
1✔
114
    if (idPattern == null) {
1✔
115
      idPattern = Util.getPropertyOption(Options.IDPATTERN.toString().toLowerCase());
1✔
116
    }
117
    if (idPattern != null && !idPattern.isEmpty()) {
1!
118
      return generatePatternedId(idPattern);
1✔
119
    }
120
    return generateTimestampId();
1✔
121
  }
122

123
  private String generatePatternedId(String pattern) {
124
    DecimalFormat fmt = new DecimalFormat(pattern);
1✔
125
    List<Change> migrations = getMigrationLoader().getMigrations();
1✔
126
    if (migrations.isEmpty()) {
1✔
127
      return fmt.format(1);
1✔
128
    }
129
    Change lastChange = migrations.get(migrations.size() - 1);
1✔
130
    try {
131
      long lastId = (Long) fmt.parse(lastChange.getId().toString());
1✔
132
      lastId++;
1✔
133
      return fmt.format(lastId);
1✔
134
    } catch (ParseException e) {
×
135
      throw new MigrationException(
×
136
          "Failed to parse last id '" + lastChange.getId() + "' using the specified idPattern '" + pattern + "'");
×
137
    }
138
  }
139

140
  private String generateTimestampId() {
141
    final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
1✔
142
    final Date now = new Date();
1✔
143
    dateFormat.setTimeZone(TimeZone.getTimeZone(environment().getTimeZone()));
1✔
144
    return dateFormat.format(now);
1✔
145
  }
146

147
  protected void copyResourceTo(String resource, File toFile) {
148
    copyResourceTo(resource, toFile, null);
1✔
149
  }
1✔
150

151
  protected void copyResourceTo(String resource, File toFile, Properties variables) {
152
    printStream.println("Creating: " + toFile.getName());
1✔
153
    try (Reader reader = Resources.getResourceAsReader(this.getClass().getClassLoader(), resource)) {
1✔
154
      copyTemplate(reader, toFile, variables);
1✔
155
    } catch (IOException e) {
×
156
      throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e,
×
157
          e);
158
    }
1✔
159
  }
1✔
160

161
  protected void copyExternalResourceTo(String resource, File toFile, Properties variables) {
162
    printStream.println("Creating: " + toFile.getName());
1✔
163
    try {
164
      File sourceFile = Path.of(resource).toFile();
1✔
165
      copyTemplate(sourceFile, toFile, variables);
1✔
166
    } catch (Exception e) {
×
167
      throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e,
×
168
          e);
169
    }
1✔
170
  }
1✔
171

172
  protected static void copyTemplate(File templateFile, File toFile, Properties variables) throws IOException {
173
    try (Reader reader = Files.newBufferedReader(templateFile.toPath())) {
1✔
174
      copyTemplate(reader, toFile, variables);
1✔
175
    }
176
  }
1✔
177

178
  protected static void copyTemplate(Reader templateReader, File toFile, Properties variables) throws IOException {
179
    VariableReplacer replacer = new VariableReplacer(variables);
1✔
180
    try (LineNumberReader reader = new LineNumberReader(templateReader);
1✔
181
        PrintWriter writer = new PrintWriter(Files.newBufferedWriter(toFile.toPath()))) {
1✔
182
      String line;
183
      while ((line = reader.readLine()) != null) {
1✔
184
        line = replacer.replace(line);
1✔
185
        writer.println(line);
1✔
186
      }
187
    }
188
  }
1✔
189

190
  protected File environmentFile() {
191
    return file(paths.getEnvPath(), options.getEnvironment() + ".properties");
1✔
192
  }
193

194
  protected File existingEnvironmentFile() {
195
    File envFile = environmentFile();
1✔
196
    if (!envFile.exists()) {
1!
197
      throw new MigrationException("Environment file missing: " + envFile.getAbsolutePath());
×
198
    }
199
    return envFile;
1✔
200
  }
201

202
  protected Environment environment() {
203
    if (environment != null) {
1✔
204
      return environment;
1✔
205
    }
206
    environment = new Environment(existingEnvironmentFile());
1✔
207
    return environment;
1✔
208
  }
209

210
  protected int getStepCountParameter(int defaultSteps, String... params) {
211
    final String stringParam = params.length > 0 ? params[0] : null;
1!
212
    if (stringParam == null || "".equals(stringParam)) {
1!
213
      return defaultSteps;
1✔
214
    }
215
    try {
216
      return Integer.parseInt(stringParam);
1✔
217
    } catch (NumberFormatException e) {
×
218
      throw new MigrationException("Invalid parameter passed to command: " + params[0]);
×
219
    }
220
  }
221

222
  protected ConnectionProvider getConnectionProvider() {
223
    try {
224
      return new JdbcConnectionProvider(getDriverClassLoader(), environment().getDriver(), environment().getUrl(),
1✔
225
          environment().getUsername(), environment().getPassword());
1✔
226
    } catch (Exception e) {
×
227
      throw new MigrationException("Error creating ScriptRunner.  Cause: " + e, e);
×
228
    }
229
  }
230

231
  private ClassLoader getDriverClassLoader() {
232
    File localDriverPath = getCustomDriverPath();
1✔
233
    if (driverClassLoader != null) {
1!
234
      return driverClassLoader;
×
235
    }
236
    if (localDriverPath.exists()) {
1!
237
      try {
238
        List<URL> urlList = new ArrayList<>();
×
239
        File[] files = localDriverPath.listFiles();
×
240
        if (files != null) {
×
241
          for (File file : files) {
×
242
            String filename = file.getCanonicalPath();
×
243
            if (!filename.startsWith("/")) {
×
244
              filename = '/' + filename;
×
245
            }
246
            urlList.add(new URL("jar:file:" + filename + "!/"));
×
247
            urlList.add(new URL("file:" + filename));
×
248
          }
249
        }
250
        URL[] urls = urlList.toArray(new URL[0]);
×
251
        return new URLClassLoader(urls);
×
252
      } catch (Exception e) {
×
253
        throw new MigrationException("Error creating a driver ClassLoader. Cause: " + e, e);
×
254
      }
255
    }
256
    return null;
1✔
257
  }
258

259
  private File getCustomDriverPath() {
260
    String customDriverPath = environment().getDriverPath();
1✔
261
    if (customDriverPath != null && customDriverPath.length() > 0) {
1!
NEW
262
      return Path.of(customDriverPath).toFile();
×
263
    }
264
    return options.getPaths().getDriverPath();
1✔
265
  }
266

267
  protected MigrationLoader getMigrationLoader() {
268
    Environment env = environment();
1✔
269
    MigrationLoader migrationLoader = null;
1✔
270
    for (FileMigrationLoaderFactory factory : ServiceLoader.load(FileMigrationLoaderFactory.class)) {
1!
271
      if (migrationLoader != null) {
×
272
        throw new MigrationException("Found multiple implementations of FileMigrationLoaderFactory via SPI.");
×
273
      }
274
      migrationLoader = factory.create(paths, env);
×
275
    }
×
276
    return migrationLoader != null ? migrationLoader
1!
277
        : new FileMigrationLoader(paths.getScriptPath(), env.getScriptCharset(), env.getVariables());
1✔
278
  }
279

280
  protected MigrationHook createUpHook() {
281
    String before = environment().getHookBeforeUp();
1✔
282
    String beforeEach = environment().getHookBeforeEachUp();
1✔
283
    String afterEach = environment().getHookAfterEachUp();
1✔
284
    String after = environment().getHookAfterUp();
1✔
285
    if (before == null && beforeEach == null && afterEach == null && after == null) {
1!
286
      return null;
1✔
287
    }
288
    return createFileMigrationHook(before, beforeEach, afterEach, after);
1✔
289
  }
290

291
  protected MigrationHook createDownHook() {
292
    String before = environment().getHookBeforeDown();
1✔
293
    String beforeEach = environment().getHookBeforeEachDown();
1✔
294
    String afterEach = environment().getHookAfterEachDown();
1✔
295
    String after = environment().getHookAfterDown();
1✔
296
    if (before == null && beforeEach == null && afterEach == null && after == null) {
1!
297
      return null;
1✔
298
    }
299
    return createFileMigrationHook(before, beforeEach, afterEach, after);
1✔
300
  }
301

302
  protected MigrationHook createFileMigrationHook(String before, String beforeEach, String afterEach, String after) {
303
    HookScriptFactory factory = new FileHookScriptFactory(options.getPaths(), environment(), printStream);
1✔
304
    return new FileMigrationHook(factory.create(before), factory.create(beforeEach), factory.create(afterEach),
1✔
305
        factory.create(after));
1✔
306
  }
307

308
  protected DatabaseOperationOption getDatabaseOperationOption() {
309
    DatabaseOperationOption option = new DatabaseOperationOption();
1✔
310
    option.setChangelogTable(changelogTable());
1✔
311
    option.setStopOnError(!options.isForce());
1!
312
    option.setThrowWarning(!options.isForce() && !environment().isIgnoreWarnings());
1!
313
    option.setEscapeProcessing(false);
1✔
314
    option.setAutoCommit(environment().isAutoCommit());
1✔
315
    option.setFullLineDelimiter(environment().isFullLineDelimiter());
1✔
316
    option.setSendFullScript(environment().isSendFullScript());
1✔
317
    option.setRemoveCRs(environment().isRemoveCrs());
1✔
318
    option.setDelimiter(environment().getDelimiter());
1✔
319
    return option;
1✔
320
  }
321
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc