You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
14 KiB
301 lines
14 KiB
package de.superx.spring; |
|
|
|
import static java.util.Map.entry; |
|
|
|
import java.io.File; |
|
import java.io.FileInputStream; |
|
import java.io.FileReader; |
|
import java.io.IOException; |
|
import java.nio.charset.StandardCharsets; |
|
import java.security.InvalidKeyException; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.util.HashMap; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Map.Entry; |
|
import java.util.Properties; |
|
import java.util.Set; |
|
import java.util.stream.Collectors; |
|
import java.util.stream.Stream; |
|
|
|
import javax.crypto.BadPaddingException; |
|
import javax.crypto.Cipher; |
|
import javax.crypto.IllegalBlockSizeException; |
|
import javax.crypto.NoSuchPaddingException; |
|
import javax.crypto.spec.SecretKeySpec; |
|
|
|
import org.apache.log4j.Logger; |
|
import org.springframework.core.io.ClassPathResource; |
|
|
|
import com.zaxxer.hikari.HikariConfig; |
|
import com.zaxxer.hikari.HikariDataSource; |
|
|
|
import de.superx.servlet.SuperXManager; |
|
import de.superx.spring.config.BatchConfig; |
|
import de.superx.util.PathAndFileUtils; |
|
|
|
|
|
|
|
|
|
public class HIS1Databases { |
|
|
|
public static final String HIS1_DATABASES_PROPERTIES = "HIS1_DATABASES_PROPERTIES"; |
|
private static final byte[] KEY_DATA = { -108, -50, -5, -75, -98, 28, -116, 107 }; |
|
private static final SecretKeySpec KEY = new SecretKeySpec(KEY_DATA, "DES"); |
|
private static Cipher desCipher; |
|
|
|
private static Logger logger = Logger.getLogger(HIS1Databases.class); |
|
|
|
private HIS1Databases() { //Mit Ausnahmegenehmigung |
|
//Prevent instantiation |
|
} |
|
|
|
public static Map<String,Map<String,String>> getAllConnections() throws |
|
InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ |
|
return fetchConnections(); |
|
} |
|
|
|
public static Map<String,Map<String,String>> getValidConnections() throws |
|
InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ |
|
Map<String,Map<String,String>> conns = getAllConnections(); |
|
logger.info("Got connections: " + conns.keySet()); |
|
Map<String,Map<String,String>> validConns = new HashMap<>(); |
|
for (String key : conns.keySet()) { |
|
Map<String,String> connectionProperties = conns.get(key); |
|
if ( |
|
connectionProperties.get(DbPropertyName.database.name()) != null |
|
&&connectionProperties.get(DbPropertyName.user.name()) != null |
|
&& connectionProperties.get(DbPropertyName.url.name()) != null |
|
&& connectionProperties.get(DbPropertyName.protocol.name()) != null |
|
&& connectionProperties.get(DbPropertyName.jdbcDriver.name()) != null |
|
|
|
) { |
|
logger.info("Valid connection: " + key); |
|
validConns.put(key, connectionProperties); |
|
} else { |
|
logger.warn("Invalid connection: " + key); |
|
} |
|
} |
|
return validConns; |
|
} |
|
|
|
public static Map<String,String> getConnection(String connectionName) throws |
|
InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ |
|
Map<String,Map<String,String>> connections = fetchConnections(); |
|
return connections.get(connectionName) != null ? connections.get(connectionName) : new HashMap<>(); |
|
} |
|
|
|
private static Properties fetchProperties() { |
|
Properties props = new Properties(); |
|
String databasesPropertiesFileName = System.getProperty(HIS1_DATABASES_PROPERTIES); |
|
if (databasesPropertiesFileName != null) { |
|
File databasePropertiesFile = new File(databasesPropertiesFileName); |
|
if (databasePropertiesFile.exists()) { |
|
logger.info("Reading database properties from " + databasesPropertiesFileName); |
|
try { |
|
props.load(new FileInputStream(databasePropertiesFile)); |
|
} catch (Exception e) { |
|
logger.error("Couldn't load database properties from " + databasesPropertiesFileName,e); |
|
} |
|
} else { |
|
logger.error("HIS1_DATABASES_PROPERTIES file not found: " + databasesPropertiesFileName); |
|
} |
|
} |
|
if (props.size() == 0) { |
|
// no external properties file, loading from classpath |
|
File propFile = new File(PathAndFileUtils.getWebinfPath() + File.separator + "conf" + File.separator + "his1_databases.properties"); |
|
if (!propFile.exists()) { |
|
logger.warn("No his1_databasees.properties found. Will try to load from db.properties!"); |
|
return readDbProperties(); |
|
} |
|
try(FileInputStream s = new FileInputStream(propFile)){ |
|
props.load(s); |
|
} catch (IOException e1) { |
|
logger.fatal(String.format("Unable to access his1_databases file: %s",e1.getMessage())); |
|
e1.printStackTrace(); |
|
} |
|
} |
|
Properties manualConfig = manualConfiguration(); |
|
return mergeProperties(props, manualConfig); |
|
} |
|
|
|
private static Properties manualConfiguration() { |
|
Properties optProps = new Properties(); |
|
File poolConf = new File(PathAndFileUtils.getWebinfPath() + File.separator + "conf" + File.separator + "his1_db_poolsize.properties"); |
|
try(FileInputStream s = new FileInputStream(poolConf)){ |
|
optProps.load(s); |
|
} catch (IOException e1) { |
|
logger.info("Unable to access manual poolsize configuration\nSkipping manual poolsize configuration - setting defaults: "+e1.getMessage()); |
|
} |
|
return optProps; |
|
} |
|
|
|
private static Properties mergeProperties(Properties... properties) { |
|
return Stream.of(properties).collect(Properties::new, Map::putAll, Map::putAll); |
|
} |
|
|
|
private static Cipher getDesCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { |
|
return Cipher.getInstance("DES"); |
|
} |
|
|
|
private static String decryptDbPassword(String password) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { |
|
if(password == null || password.equals("")) { |
|
return null; |
|
}else if(!password.startsWith("sx_des")) { |
|
return password; |
|
} |
|
|
|
if(desCipher == null) { |
|
desCipher = getDesCipher(); |
|
} |
|
|
|
String[] stext = password.substring(7).split("#"); |
|
byte[] bstext = new byte[stext.length]; |
|
for(int i = 0; i<stext.length;i++) { |
|
bstext[i] = Byte.parseByte(stext[i]); |
|
} |
|
desCipher.init(Cipher.DECRYPT_MODE, KEY); |
|
byte[] decrypted = desCipher.doFinal(bstext); |
|
return new String(decrypted, StandardCharsets.UTF_8); |
|
} |
|
|
|
private static Map<String,Map<String,String>> fetchConnections() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { |
|
Map<String,Map<String,String>> connections = new HashMap<>(); |
|
Properties props = fetchProperties(); |
|
String[] dbconnections = props.get("dbconnections").toString().split(","); |
|
final Set<String> propertyNames = props.stringPropertyNames(); |
|
for (String s : dbconnections) { |
|
Map<String,String> properties = new HashMap<>(); |
|
logger.info(String.format("%s connection retrieved from HISinOne", s)); |
|
List<String> keys = propertyNames.stream().filter(it -> it.startsWith("db." + s + ".")).collect(Collectors.toList()); |
|
for (String key : keys) { |
|
String value = props.getProperty(key); |
|
if (key.endsWith("password")) { |
|
value = decryptDbPassword(value.trim()); |
|
} |
|
properties.put(key.substring(key.lastIndexOf('.') + 1), value); |
|
} |
|
connections.put(s, properties); |
|
} |
|
return connections; |
|
} |
|
|
|
private static Properties readDbProperties() { |
|
Properties properties = new Properties(); |
|
String webinfpfad=PathAndFileUtils.getWebinfPath(); |
|
|
|
File dbPropertiesFile = new File( webinfpfad |
|
+ File.separator |
|
+ "db.properties"); |
|
if (dbPropertiesFile.exists()) { |
|
try { |
|
Properties dbProperties = new Properties(); |
|
dbProperties.load(new FileReader(dbPropertiesFile)); |
|
properties = convertDbProperties(dbProperties); |
|
} catch (Exception e) { |
|
throw new RuntimeException("Couldn't read db.properties!", e); |
|
} |
|
} else { |
|
throw new RuntimeException("Couldn't read " + dbPropertiesFile.getAbsolutePath() + " because file doesn't exist!"); |
|
} |
|
return properties; |
|
} |
|
|
|
public static Properties convertDbProperties(Properties dbProperties) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { |
|
Properties his1Properties = new Properties(); |
|
String jdbcUrl = dbProperties.getProperty("connectionURL"); |
|
String user = dbProperties.getProperty("connectionName"); |
|
String password = decryptDbPassword(dbProperties.getProperty("connectionPassword")); |
|
String sslmode = dbProperties.getProperty("sslmode"); |
|
String driver = dbProperties.getProperty("driverName"); |
|
String[] urlParts = analyzeJdbcUrl(jdbcUrl); |
|
String prefix = "db.eduetl."; |
|
Map<String,String> config = Map.ofEntries( |
|
entry(prefix + DbPropertyName.user.name(), user), |
|
entry(prefix + DbPropertyName.password.name(), password), |
|
entry(prefix + DbPropertyName.jdbcDriver.name(), driver), |
|
entry(prefix + DbPropertyName.ssl.name(), sslmode != null && sslmode.equalsIgnoreCase("require") ? "true" : "false"), |
|
entry(prefix + DbPropertyName.protocol.name(), urlParts[0]), |
|
entry(prefix + DbPropertyName.host.name(), urlParts[1]), |
|
entry(prefix + DbPropertyName.dbserverport.name(), urlParts[2]), |
|
entry(prefix + DbPropertyName.database.name(), urlParts[3]), |
|
entry(prefix + DbPropertyName.url.name(), jdbcUrl), |
|
entry(DbPropertyName.dbconnections.name(), "eduetl") |
|
|
|
); |
|
his1Properties.putAll(config); |
|
return his1Properties; |
|
} |
|
|
|
public static String[] analyzeJdbcUrl(String jdbcUrl) { |
|
int protocolEndIndex = jdbcUrl.indexOf("//"); |
|
int portBeginIndex = jdbcUrl.indexOf(":", protocolEndIndex); |
|
int dbBeginIndex = jdbcUrl.indexOf("/", portBeginIndex); |
|
int dbEndIndex = jdbcUrl.indexOf("?"); String protocol = jdbcUrl.substring(0, protocolEndIndex); |
|
String host = jdbcUrl.substring(protocolEndIndex + 2, portBeginIndex); |
|
String port = jdbcUrl.substring(portBeginIndex + 1, dbBeginIndex); |
|
String db = jdbcUrl.substring(dbBeginIndex + 1, dbEndIndex == -1 ? jdbcUrl.length() : dbEndIndex); |
|
return new String[] { |
|
protocol, host, port, db |
|
}; |
|
} |
|
|
|
|
|
public static HikariDataSource convertConnection(String connectionName, Map<String,String> co, long leakDetectionThreshold, long connectionTimeout){ |
|
HikariConfig conf = new HikariConfig(); |
|
conf.setUsername(co.get(DbPropertyName.user.name())); |
|
conf.setPassword(co.get(DbPropertyName.password.name())); |
|
conf.setDriverClassName(co.get(DbPropertyName.jdbcDriver.name())); |
|
conf.setJdbcUrl(co.get(DbPropertyName.url.name())); |
|
if (co.get(DbPropertyName.pgschema.name()) != null) { |
|
conf.setSchema(co.get(DbPropertyName.pgschema.name())); |
|
} else if ("hisinone".equals(connectionName)) { |
|
conf.setSchema("hisinone"); |
|
} |
|
if (co.get(DbPropertyName.reconnectAfter.name()) != null) { |
|
// Angabe reconnectAfter in der databases.xml ist in Minuten! |
|
conf.setMaxLifetime(Long.parseLong(co.get(DbPropertyName.reconnectAfter.name())) * 60000); |
|
} |
|
conf.setPoolName(connectionName); |
|
conf.setLeakDetectionThreshold(leakDetectionThreshold); |
|
conf.setConnectionTimeout(connectionTimeout); |
|
// in HISinOne-BI context: Set PostgreSQL variables to be consistent with |
|
// HISinOne itself (which also does some settings - see "DbHandler") |
|
if (co.get(DbPropertyName.jdbcDriver.name()).equals(org.postgresql.Driver.class.getCanonicalName()) && |
|
System.getProperty(SuperXManager.SUPER_X_HISINONE_VERSION) != null) { |
|
conf.setConnectionInitSql("SET standard_conforming_strings TO 'off'; SET datestyle TO 'ISO, DMY';"); |
|
} |
|
|
|
String minConnectionsStr = co.get("poolsize_min"); |
|
String maxConnectionsStr = co.get("poolsize_max"); |
|
int defaultPoolSize = "eduetl".equals(connectionName) ? BatchConfig.EDUETL_POOL_SIZE : BatchConfig.DEFAULT_POOL_SIZE; |
|
int maxConnections = maxConnectionsStr != null ? Integer.parseInt(maxConnectionsStr) : defaultPoolSize; |
|
int minConnections = minConnectionsStr != null ? Integer.parseInt(minConnectionsStr) : maxConnections; |
|
conf.setMinimumIdle(minConnections); |
|
logger.info(String.format("HikariPool for %s initialized with a minimum of %s idle connections.", connectionName, Integer.toString(minConnections))); |
|
conf.setMaximumPoolSize(maxConnections); |
|
logger.info(String.format("HikariPool for %s initialized with a maximum of %s connections", connectionName, Integer.toString(maxConnections))); |
|
addDataSourceProperties(conf, co); |
|
conf.setExceptionOverrideClassName("de.superx.db.HikariSqlExceptionOverride"); |
|
HikariDataSource ds = null; |
|
try { |
|
ds = new HikariDataSource(conf); |
|
} catch(Exception ex) { |
|
logger.error("Couldn't create Hikaripool for " + connectionName, ex); |
|
} |
|
return ds; |
|
} |
|
|
|
|
|
private static void addDataSourceProperties(HikariConfig conf, Map<String, String> co) { |
|
for (Entry<String,String> e : co.entrySet()) { |
|
if (!DbPropertyName.isStandard(e.getKey())) { |
|
conf.addDataSourceProperty(e.getKey(), e.getValue()); |
|
logger.debug("Set additional db property " + e.getKey() + " to " + e.getValue()); |
|
} |
|
} |
|
conf.addDataSourceProperty("ApplicationName", "HIS-BI"); |
|
|
|
} |
|
|
|
}
|
|
|