View Javadoc
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;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.nio.charset.Charset;
23  import java.nio.file.Files;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Properties;
31  
32  public class Environment {
33  
34    public static final String CHANGELOG = "changelog";
35  
36    private enum SETTING_KEY {
37      TIME_ZONE,
38  
39      DELIMITER,
40  
41      SCRIPT_CHAR_SET,
42  
43      FULL_LINE_DELIMITER,
44  
45      SEND_FULL_SCRIPT,
46  
47      AUTO_COMMIT,
48  
49      REMOVE_CRS,
50  
51      IGNORE_WARNINGS,
52  
53      DRIVER_PATH,
54  
55      DRIVER,
56  
57      URL,
58  
59      USERNAME,
60  
61      PASSWORD,
62  
63      HOOK_BEFORE_UP,
64  
65      HOOK_BEFORE_EACH_UP,
66  
67      HOOK_AFTER_EACH_UP,
68  
69      HOOK_AFTER_UP,
70  
71      HOOK_BEFORE_DOWN,
72  
73      HOOK_BEFORE_EACH_DOWN,
74  
75      HOOK_AFTER_EACH_DOWN,
76  
77      HOOK_AFTER_DOWN,
78  
79      HOOK_BEFORE_NEW,
80  
81      HOOK_AFTER_NEW,
82  
83      HOOK_BEFORE_SCRIPT,
84  
85      HOOK_BEFORE_EACH_SCRIPT,
86  
87      HOOK_AFTER_EACH_SCRIPT,
88  
89      HOOK_AFTER_SCRIPT;
90  
91      @Override
92      public String toString() {
93        return this.name().toLowerCase(Locale.ENGLISH);
94      }
95    }
96  
97    private static final List<String> SETTING_KEYS;
98  
99    static {
100     ArrayList<String> list = new ArrayList<>();
101     SETTING_KEY[] keys = SETTING_KEY.values();
102     for (SETTING_KEY key : keys) {
103       list.add(key.toString());
104     }
105     SETTING_KEYS = Collections.unmodifiableList(list);
106   }
107 
108   private final String timeZone;
109   private final String delimiter;
110   private final String scriptCharset;
111   private final boolean fullLineDelimiter;
112   private final boolean sendFullScript;
113   private final boolean autoCommit;
114   private final boolean removeCrs;
115   private final boolean ignoreWarnings;
116   private final String driverPath;
117   private final String driver;
118   private final String url;
119   private final String username;
120   private final String password;
121 
122   private final String hookBeforeUp;
123   private final String hookBeforeEachUp;
124   private final String hookAfterEachUp;
125   private final String hookAfterUp;
126   private final String hookBeforeDown;
127   private final String hookBeforeEachDown;
128   private final String hookAfterEachDown;
129   private final String hookAfterDown;
130 
131   private final String hookBeforeNew;
132   private final String hookAfterNew;
133 
134   private final String hookBeforeScript;
135   private final String hookBeforeEachScript;
136   private final String hookAfterEachScript;
137   private final String hookAfterScript;
138 
139   /**
140    * Prefix used to lookup environment variable or system property.
141    */
142   private static final String PREFIX = "MIGRATIONS_";
143   private final Map<String, String> envVars = System.getenv();
144   private final Properties sysProps = System.getProperties();
145   private final Properties variables = new Properties();
146 
147   private final VariableReplacer parser = new VariableReplacer(Arrays.asList(sysProps, envVars));
148 
149   public Environment(File file) {
150     Properties prop = mergeProperties(file);
151 
152     this.timeZone = readProperty(prop, SETTING_KEY.TIME_ZONE.toString(), "GMT+0:00");
153     this.delimiter = readProperty(prop, SETTING_KEY.DELIMITER.toString(), ";");
154     this.scriptCharset = readProperty(prop, SETTING_KEY.SCRIPT_CHAR_SET.toString(),
155         Charset.defaultCharset().toString());
156     this.fullLineDelimiter = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.FULL_LINE_DELIMITER.toString()));
157     this.sendFullScript = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.SEND_FULL_SCRIPT.toString()));
158     this.autoCommit = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.AUTO_COMMIT.toString()));
159     this.removeCrs = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.REMOVE_CRS.toString()));
160     this.ignoreWarnings = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.IGNORE_WARNINGS.toString(), "true"));
161 
162     this.driverPath = readProperty(prop, SETTING_KEY.DRIVER_PATH.toString());
163     this.driver = readProperty(prop, SETTING_KEY.DRIVER.toString());
164     this.url = readProperty(prop, SETTING_KEY.URL.toString());
165     this.username = readProperty(prop, SETTING_KEY.USERNAME.toString());
166     this.password = readProperty(prop, SETTING_KEY.PASSWORD.toString());
167 
168     this.hookBeforeUp = readProperty(prop, SETTING_KEY.HOOK_BEFORE_UP.toString());
169     this.hookBeforeEachUp = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_UP.toString());
170     this.hookAfterEachUp = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_UP.toString());
171     this.hookAfterUp = readProperty(prop, SETTING_KEY.HOOK_AFTER_UP.toString());
172     this.hookBeforeDown = readProperty(prop, SETTING_KEY.HOOK_BEFORE_DOWN.toString());
173     this.hookBeforeEachDown = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_DOWN.toString());
174     this.hookAfterEachDown = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_DOWN.toString());
175     this.hookAfterDown = readProperty(prop, SETTING_KEY.HOOK_AFTER_DOWN.toString());
176 
177     this.hookBeforeNew = readProperty(prop, SETTING_KEY.HOOK_BEFORE_NEW.toString());
178     this.hookAfterNew = readProperty(prop, SETTING_KEY.HOOK_AFTER_NEW.toString());
179 
180     this.hookBeforeScript = readProperty(prop, SETTING_KEY.HOOK_BEFORE_SCRIPT.toString());
181     this.hookBeforeEachScript = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_SCRIPT.toString());
182     this.hookAfterEachScript = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_SCRIPT.toString());
183     this.hookAfterScript = readProperty(prop, SETTING_KEY.HOOK_AFTER_SCRIPT.toString());
184 
185     // User defined variables.
186     prop.entrySet().stream().filter(e -> !SETTING_KEYS.contains(e.getKey()))
187         .forEach(e -> variables.put(e.getKey(), parser.replace((String) e.getValue())));
188   }
189 
190   private Properties mergeProperties(File file) {
191     // 1. Load from file.
192     Properties prop = loadPropertiesFromFile(file);
193     // 2. Read environment variables (existing entries are overwritten).
194     envVars.entrySet().stream().filter(e -> isMigrationsKey(e.getKey()))
195         .forEach(e -> prop.put(normalizeKey(e.getKey()), e.getValue()));
196     // 3. Read system properties (existing entries are overwritten).
197     sysProps.entrySet().stream().filter(e -> isMigrationsKey((String) e.getKey()))
198         .forEach(e -> prop.put(normalizeKey((String) e.getKey()), e.getValue()));
199     return prop;
200   }
201 
202   private String normalizeKey(String key) {
203     return key.substring(PREFIX.length()).toLowerCase(Locale.ENGLISH);
204   }
205 
206   private boolean isMigrationsKey(String key) {
207     return key.length() > PREFIX.length() && key.toUpperCase(Locale.ENGLISH).startsWith(PREFIX);
208   }
209 
210   private Properties loadPropertiesFromFile(File file) {
211     Properties properties = new Properties();
212     try (InputStream inputStream = Files.newInputStream(file.toPath())) {
213       properties.load(inputStream);
214       return properties;
215     } catch (FileNotFoundException e) {
216       throw new MigrationException("Environment file missing: " + file.getAbsolutePath());
217     } catch (IOException e) {
218       throw new MigrationException("Error loading environment properties.  Cause: " + e, e);
219     }
220   }
221 
222   private String readProperty(Properties properties, String propertyKey) {
223     return readProperty(properties, propertyKey, null);
224   }
225 
226   private String readProperty(Properties properties, String propertyKey, String defaultValue) {
227     String property = properties.getProperty(propertyKey, defaultValue);
228     return property == null ? null : parser.replace(property);
229   }
230 
231   public String getTimeZone() {
232     return timeZone;
233   }
234 
235   public String getDelimiter() {
236     return delimiter;
237   }
238 
239   public String getScriptCharset() {
240     return scriptCharset;
241   }
242 
243   public boolean isFullLineDelimiter() {
244     return fullLineDelimiter;
245   }
246 
247   public boolean isSendFullScript() {
248     return sendFullScript;
249   }
250 
251   public boolean isAutoCommit() {
252     return autoCommit;
253   }
254 
255   public boolean isRemoveCrs() {
256     return removeCrs;
257   }
258 
259   public boolean isIgnoreWarnings() {
260     return ignoreWarnings;
261   }
262 
263   public String getDriverPath() {
264     return driverPath;
265   }
266 
267   public String getDriver() {
268     return driver;
269   }
270 
271   public String getUrl() {
272     return url;
273   }
274 
275   public String getUsername() {
276     return username;
277   }
278 
279   public String getPassword() {
280     return password;
281   }
282 
283   public String getHookBeforeUp() {
284     return hookBeforeUp;
285   }
286 
287   public String getHookBeforeEachUp() {
288     return hookBeforeEachUp;
289   }
290 
291   public String getHookAfterEachUp() {
292     return hookAfterEachUp;
293   }
294 
295   public String getHookAfterUp() {
296     return hookAfterUp;
297   }
298 
299   public String getHookBeforeDown() {
300     return hookBeforeDown;
301   }
302 
303   public String getHookBeforeEachDown() {
304     return hookBeforeEachDown;
305   }
306 
307   public String getHookAfterEachDown() {
308     return hookAfterEachDown;
309   }
310 
311   public String getHookAfterDown() {
312     return hookAfterDown;
313   }
314 
315   public String getHookBeforeNew() {
316     return hookBeforeNew;
317   }
318 
319   public String getHookAfterNew() {
320     return hookAfterNew;
321   }
322 
323   public String getHookBeforeScript() {
324     return hookBeforeScript;
325   }
326 
327   public String getHookBeforeEachScript() {
328     return hookBeforeEachScript;
329   }
330 
331   public String getHookAfterEachScript() {
332     return hookAfterEachScript;
333   }
334 
335   public String getHookAfterScript() {
336     return hookAfterScript;
337   }
338 
339   public Properties getVariables() {
340     return variables;
341   }
342 }