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> getAllConnections() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ return fetchConnections(); } public static Map> getValidConnections() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ Map> conns = getAllConnections(); logger.info("Got connections: " + conns.keySet()); Map> validConns = new HashMap<>(); for (String key : conns.keySet()) { Map 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 getConnection(String connectionName) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ Map> 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> fetchConnections() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Map> connections = new HashMap<>(); Properties props = fetchProperties(); String[] dbconnections = props.get("dbconnections").toString().split(","); final Set propertyNames = props.stringPropertyNames(); for (String s : dbconnections) { Map properties = new HashMap<>(); logger.info(String.format("%s connection retrieved from HISinOne", s)); List 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 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 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 co) { for (Entry 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"); } }