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