1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.FileReader;
22 import java.io.FileWriter;
23 import java.io.IOException;
24 import java.io.LineNumberReader;
25 import java.io.OutputStream;
26 import java.io.PrintStream;
27 import java.io.PrintWriter;
28 import java.io.Reader;
29 import java.net.URL;
30 import java.net.URLClassLoader;
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;
69
70 protected final SelectedOptions options;
71 protected final SelectedPaths paths;
72
73 protected BaseCommand(SelectedOptions selectedOptions) {
74 this.options = selectedOptions;
75 this.paths = selectedOptions.getPaths();
76 if (options.isQuiet()) {
77 this.printStream = new PrintStream(new OutputStream() {
78 @Override
79 public void write(int b) {
80
81 }
82 });
83 }
84 }
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;
99 }
100
101 protected String changelogTable() {
102 return environment().getVariables().getProperty(Environment.CHANGELOG, "CHANGELOG");
103 }
104
105 protected String getNextIDAsString() {
106 try {
107
108 Thread.sleep(1000);
109 } catch (InterruptedException e) {
110
111 Thread.currentThread().interrupt();
112 }
113 String idPattern = options.getIdPattern();
114 if (idPattern == null) {
115 idPattern = Util.getPropertyOption(Options.IDPATTERN.toString().toLowerCase());
116 }
117 if (idPattern != null && !idPattern.isEmpty()) {
118 return generatePatternedId(idPattern);
119 }
120 return generateTimestampId();
121 }
122
123 private String generatePatternedId(String pattern) {
124 DecimalFormat fmt = new DecimalFormat(pattern);
125 List<Change> migrations = getMigrationLoader().getMigrations();
126 if (migrations.isEmpty()) {
127 return fmt.format(1);
128 }
129 Change lastChange = migrations.get(migrations.size() - 1);
130 try {
131 long lastId = (Long) fmt.parse(lastChange.getId().toString());
132 lastId++;
133 return fmt.format(lastId);
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);
142 final Date now = new Date();
143 dateFormat.setTimeZone(TimeZone.getTimeZone(environment().getTimeZone()));
144 return dateFormat.format(now);
145 }
146
147 protected void copyResourceTo(String resource, File toFile) {
148 copyResourceTo(resource, toFile, null);
149 }
150
151 protected void copyResourceTo(String resource, File toFile, Properties variables) {
152 printStream.println("Creating: " + toFile.getName());
153 try (Reader reader = Resources.getResourceAsReader(this.getClass().getClassLoader(), resource)) {
154 copyTemplate(reader, toFile, variables);
155 } catch (IOException e) {
156 throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ". Cause: " + e,
157 e);
158 }
159 }
160
161 protected void copyExternalResourceTo(String resource, File toFile, Properties variables) {
162 printStream.println("Creating: " + toFile.getName());
163 try {
164 File sourceFile = new File(resource);
165 copyTemplate(sourceFile, toFile, variables);
166 } catch (Exception e) {
167 throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ". Cause: " + e,
168 e);
169 }
170 }
171
172 protected static void copyTemplate(File templateFile, File toFile, Properties variables) throws IOException {
173 try (FileReader reader = new FileReader(templateFile)) {
174 copyTemplate(reader, toFile, variables);
175 }
176 }
177
178 protected static void copyTemplate(Reader templateReader, File toFile, Properties variables) throws IOException {
179 VariableReplacer replacer = new VariableReplacer(variables);
180 try (LineNumberReader reader = new LineNumberReader(templateReader);
181 PrintWriter writer = new PrintWriter(new FileWriter(toFile))) {
182 String line;
183 while ((line = reader.readLine()) != null) {
184 line = replacer.replace(line);
185 writer.println(line);
186 }
187 }
188 }
189
190 protected File environmentFile() {
191 return file(paths.getEnvPath(), options.getEnvironment() + ".properties");
192 }
193
194 protected File existingEnvironmentFile() {
195 File envFile = environmentFile();
196 if (!envFile.exists()) {
197 throw new MigrationException("Environment file missing: " + envFile.getAbsolutePath());
198 }
199 return envFile;
200 }
201
202 protected Environment environment() {
203 if (environment != null) {
204 return environment;
205 }
206 environment = new Environment(existingEnvironmentFile());
207 return environment;
208 }
209
210 protected int getStepCountParameter(int defaultSteps, String... params) {
211 final String stringParam = params.length > 0 ? params[0] : null;
212 if (stringParam == null || "".equals(stringParam)) {
213 return defaultSteps;
214 }
215 try {
216 return Integer.parseInt(stringParam);
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(),
225 environment().getUsername(), environment().getPassword());
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();
233 if (driverClassLoader != null) {
234 return driverClassLoader;
235 }
236 if (localDriverPath.exists()) {
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;
257 }
258
259 private File getCustomDriverPath() {
260 String customDriverPath = environment().getDriverPath();
261 if (customDriverPath != null && customDriverPath.length() > 0) {
262 return new File(customDriverPath);
263 }
264 return options.getPaths().getDriverPath();
265 }
266
267 protected MigrationLoader getMigrationLoader() {
268 Environment env = environment();
269 MigrationLoader migrationLoader = null;
270 for (FileMigrationLoaderFactory factory : ServiceLoader.load(FileMigrationLoaderFactory.class)) {
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
277 : new FileMigrationLoader(paths.getScriptPath(), env.getScriptCharset(), env.getVariables());
278 }
279
280 protected MigrationHook createUpHook() {
281 String before = environment().getHookBeforeUp();
282 String beforeEach = environment().getHookBeforeEachUp();
283 String afterEach = environment().getHookAfterEachUp();
284 String after = environment().getHookAfterUp();
285 if (before == null && beforeEach == null && afterEach == null && after == null) {
286 return null;
287 }
288 return createFileMigrationHook(before, beforeEach, afterEach, after);
289 }
290
291 protected MigrationHook createDownHook() {
292 String before = environment().getHookBeforeDown();
293 String beforeEach = environment().getHookBeforeEachDown();
294 String afterEach = environment().getHookAfterEachDown();
295 String after = environment().getHookAfterDown();
296 if (before == null && beforeEach == null && afterEach == null && after == null) {
297 return null;
298 }
299 return createFileMigrationHook(before, beforeEach, afterEach, after);
300 }
301
302 protected MigrationHook createFileMigrationHook(String before, String beforeEach, String afterEach, String after) {
303 HookScriptFactory factory = new FileHookScriptFactory(options.getPaths(), environment(), printStream);
304 return new FileMigrationHook(factory.create(before), factory.create(beforeEach), factory.create(afterEach),
305 factory.create(after));
306 }
307
308 protected DatabaseOperationOption getDatabaseOperationOption() {
309 DatabaseOperationOption option = new DatabaseOperationOption();
310 option.setChangelogTable(changelogTable());
311 option.setStopOnError(!options.isForce());
312 option.setThrowWarning(!options.isForce() && !environment().isIgnoreWarnings());
313 option.setEscapeProcessing(false);
314 option.setAutoCommit(environment().isAutoCommit());
315 option.setFullLineDelimiter(environment().isFullLineDelimiter());
316 option.setSendFullScript(environment().isSendFullScript());
317 option.setRemoveCRs(environment().isRemoveCrs());
318 option.setDelimiter(environment().getDelimiter());
319 return option;
320 }
321 }