Environment.java
/*
* Copyright 2010-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.migration;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
public class Environment {
public static final String CHANGELOG = "changelog";
private enum SETTING_KEY {
TIME_ZONE,
DELIMITER,
SCRIPT_CHAR_SET,
FULL_LINE_DELIMITER,
SEND_FULL_SCRIPT,
AUTO_COMMIT,
REMOVE_CRS,
IGNORE_WARNINGS,
DRIVER_PATH,
DRIVER,
URL,
USERNAME,
PASSWORD,
HOOK_BEFORE_UP,
HOOK_BEFORE_EACH_UP,
HOOK_AFTER_EACH_UP,
HOOK_AFTER_UP,
HOOK_BEFORE_DOWN,
HOOK_BEFORE_EACH_DOWN,
HOOK_AFTER_EACH_DOWN,
HOOK_AFTER_DOWN,
HOOK_BEFORE_NEW,
HOOK_AFTER_NEW,
HOOK_BEFORE_SCRIPT,
HOOK_BEFORE_EACH_SCRIPT,
HOOK_AFTER_EACH_SCRIPT,
HOOK_AFTER_SCRIPT;
@Override
public String toString() {
return this.name().toLowerCase(Locale.ENGLISH);
}
}
private static final List<String> SETTING_KEYS;
static {
ArrayList<String> list = new ArrayList<>();
SETTING_KEY[] keys = SETTING_KEY.values();
for (SETTING_KEY key : keys) {
list.add(key.toString());
}
SETTING_KEYS = Collections.unmodifiableList(list);
}
private final String timeZone;
private final String delimiter;
private final String scriptCharset;
private final boolean fullLineDelimiter;
private final boolean sendFullScript;
private final boolean autoCommit;
private final boolean removeCrs;
private final boolean ignoreWarnings;
private final String driverPath;
private final String driver;
private final String url;
private final String username;
private final String password;
private final String hookBeforeUp;
private final String hookBeforeEachUp;
private final String hookAfterEachUp;
private final String hookAfterUp;
private final String hookBeforeDown;
private final String hookBeforeEachDown;
private final String hookAfterEachDown;
private final String hookAfterDown;
private final String hookBeforeNew;
private final String hookAfterNew;
private final String hookBeforeScript;
private final String hookBeforeEachScript;
private final String hookAfterEachScript;
private final String hookAfterScript;
/**
* Prefix used to lookup environment variable or system property.
*/
private static final String PREFIX = "MIGRATIONS_";
private final Map<String, String> envVars = System.getenv();
private final Properties sysProps = System.getProperties();
private final Properties variables = new Properties();
private final VariableReplacer parser = new VariableReplacer(Arrays.asList(sysProps, envVars));
public Environment(File file) {
Properties prop = mergeProperties(file);
this.timeZone = readProperty(prop, SETTING_KEY.TIME_ZONE.toString(), "GMT+0:00");
this.delimiter = readProperty(prop, SETTING_KEY.DELIMITER.toString(), ";");
this.scriptCharset = readProperty(prop, SETTING_KEY.SCRIPT_CHAR_SET.toString(),
Charset.defaultCharset().toString());
this.fullLineDelimiter = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.FULL_LINE_DELIMITER.toString()));
this.sendFullScript = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.SEND_FULL_SCRIPT.toString()));
this.autoCommit = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.AUTO_COMMIT.toString()));
this.removeCrs = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.REMOVE_CRS.toString()));
this.ignoreWarnings = Boolean.parseBoolean(readProperty(prop, SETTING_KEY.IGNORE_WARNINGS.toString(), "true"));
this.driverPath = readProperty(prop, SETTING_KEY.DRIVER_PATH.toString());
this.driver = readProperty(prop, SETTING_KEY.DRIVER.toString());
this.url = readProperty(prop, SETTING_KEY.URL.toString());
this.username = readProperty(prop, SETTING_KEY.USERNAME.toString());
this.password = readProperty(prop, SETTING_KEY.PASSWORD.toString());
this.hookBeforeUp = readProperty(prop, SETTING_KEY.HOOK_BEFORE_UP.toString());
this.hookBeforeEachUp = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_UP.toString());
this.hookAfterEachUp = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_UP.toString());
this.hookAfterUp = readProperty(prop, SETTING_KEY.HOOK_AFTER_UP.toString());
this.hookBeforeDown = readProperty(prop, SETTING_KEY.HOOK_BEFORE_DOWN.toString());
this.hookBeforeEachDown = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_DOWN.toString());
this.hookAfterEachDown = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_DOWN.toString());
this.hookAfterDown = readProperty(prop, SETTING_KEY.HOOK_AFTER_DOWN.toString());
this.hookBeforeNew = readProperty(prop, SETTING_KEY.HOOK_BEFORE_NEW.toString());
this.hookAfterNew = readProperty(prop, SETTING_KEY.HOOK_AFTER_NEW.toString());
this.hookBeforeScript = readProperty(prop, SETTING_KEY.HOOK_BEFORE_SCRIPT.toString());
this.hookBeforeEachScript = readProperty(prop, SETTING_KEY.HOOK_BEFORE_EACH_SCRIPT.toString());
this.hookAfterEachScript = readProperty(prop, SETTING_KEY.HOOK_AFTER_EACH_SCRIPT.toString());
this.hookAfterScript = readProperty(prop, SETTING_KEY.HOOK_AFTER_SCRIPT.toString());
// User defined variables.
prop.entrySet().stream().filter(e -> !SETTING_KEYS.contains(e.getKey()))
.forEach(e -> variables.put(e.getKey(), parser.replace((String) e.getValue())));
}
private Properties mergeProperties(File file) {
// 1. Load from file.
Properties prop = loadPropertiesFromFile(file);
// 2. Read environment variables (existing entries are overwritten).
envVars.entrySet().stream().filter(e -> isMigrationsKey(e.getKey()))
.forEach(e -> prop.put(normalizeKey(e.getKey()), e.getValue()));
// 3. Read system properties (existing entries are overwritten).
sysProps.entrySet().stream().filter(e -> isMigrationsKey((String) e.getKey()))
.forEach(e -> prop.put(normalizeKey((String) e.getKey()), e.getValue()));
return prop;
}
private String normalizeKey(String key) {
return key.substring(PREFIX.length()).toLowerCase(Locale.ENGLISH);
}
private boolean isMigrationsKey(String key) {
return key.length() > PREFIX.length() && key.toUpperCase(Locale.ENGLISH).startsWith(PREFIX);
}
private Properties loadPropertiesFromFile(File file) {
Properties properties = new Properties();
try (FileInputStream inputStream = new FileInputStream(file)) {
properties.load(inputStream);
return properties;
} catch (FileNotFoundException e) {
throw new MigrationException("Environment file missing: " + file.getAbsolutePath());
} catch (IOException e) {
throw new MigrationException("Error loading environment properties. Cause: " + e, e);
}
}
private String readProperty(Properties properties, String propertyKey) {
return readProperty(properties, propertyKey, null);
}
private String readProperty(Properties properties, String propertyKey, String defaultValue) {
String property = properties.getProperty(propertyKey, defaultValue);
return property == null ? null : parser.replace(property);
}
public String getTimeZone() {
return timeZone;
}
public String getDelimiter() {
return delimiter;
}
public String getScriptCharset() {
return scriptCharset;
}
public boolean isFullLineDelimiter() {
return fullLineDelimiter;
}
public boolean isSendFullScript() {
return sendFullScript;
}
public boolean isAutoCommit() {
return autoCommit;
}
public boolean isRemoveCrs() {
return removeCrs;
}
public boolean isIgnoreWarnings() {
return ignoreWarnings;
}
public String getDriverPath() {
return driverPath;
}
public String getDriver() {
return driver;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getHookBeforeUp() {
return hookBeforeUp;
}
public String getHookBeforeEachUp() {
return hookBeforeEachUp;
}
public String getHookAfterEachUp() {
return hookAfterEachUp;
}
public String getHookAfterUp() {
return hookAfterUp;
}
public String getHookBeforeDown() {
return hookBeforeDown;
}
public String getHookBeforeEachDown() {
return hookBeforeEachDown;
}
public String getHookAfterEachDown() {
return hookAfterEachDown;
}
public String getHookAfterDown() {
return hookAfterDown;
}
public String getHookBeforeNew() {
return hookBeforeNew;
}
public String getHookAfterNew() {
return hookAfterNew;
}
public String getHookBeforeScript() {
return hookBeforeScript;
}
public String getHookBeforeEachScript() {
return hookBeforeEachScript;
}
public String getHookAfterEachScript() {
return hookAfterEachScript;
}
public String getHookAfterScript() {
return hookAfterScript;
}
public Properties getVariables() {
return variables;
}
}