DBPool.java

package sk.iway.iwcm;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.util.PropertyElf;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXParseException;
import sk.iway.iwcm.doc.DebugTimer;
import sk.iway.iwcm.system.ConfDB;
import sk.iway.iwcm.system.adminlog.AdminlogNotifyManager;
import sk.iway.iwcm.system.cluster.ClusterDB;
import sk.iway.iwcm.system.dbpool.ConfigurableDataSource;
import sk.iway.iwcm.system.dbpool.WebJetHikariDataSource;
import sk.iway.iwcm.system.jpa.JpaTools;
import sk.iway.iwcm.system.jpa.WebJETPersistenceProvider;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.persistence.EntityManagerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 *  Database pooling s pouzitim DBCP
 *
 *@Title        Interway Content Management
 *@Company      Interway s.r.o. (www.interway.sk)
 *@Copyright    Interway s.r.o. (c) 2001-2002
 *@author       $Author$
 *@version      $Revision$
 *@created      $Date$
 */
public class DBPool
{
	private static DBPool instance = null;
	private static Hashtable<String, ConfigurableDataSource> dataSourcesTable = null; //NOSONAR
	private static Map<String, EntityManagerFactory> entityManagerFactories;
	private static Map<String, DataSource> externalDataSources = null;
	private static AtomicBoolean hasPrintedStackTrace = new AtomicBoolean(false);

	/**
	 * Je to singleton, ziskame instanciu
	 * @return
	 */
	public static synchronized DBPool getInstance()
	{
		if (instance == null)
		{
			instance = new DBPool();
			instance.initialize();
		}
		return(instance);
	}

	/**
	 * Toto sa pouzije iba pri setupe, inokedy sa nesmie pouzit
	 * @param forceRefresh
	 * @return
	 */
	public static synchronized DBPool getInstance(boolean forceRefresh)
	{
		if (instance == null || forceRefresh)
		{
			instance = new DBPool();
			instance.initialize();
		}
		return(instance);
	}

	/**
	 * Nacitanie konfiguracie z poolman.xml (aj ked sa pouziva DBCP)
	 *
	 */
	private synchronized void initialize()
	{
		dataSourcesTable = new Hashtable<>();

		// inicializuj
		Logger.println(this,"DBPool: init");

		String dbname;
		String driver;
		String username;
		String password;
		String url;

		int minActive;
		int maxActive;

		boolean autoCommit;
		boolean readOnly;
		String transactionIsolation;

		int removeAbandonedTimeout;

		String systemIwcmDBName = InitServlet.getContextDbName();
		if (Tools.isEmpty(systemIwcmDBName)) systemIwcmDBName = System.getProperty("webjetDbname");
		Logger.println(this, "systemIwcmDBName="+systemIwcmDBName);

		//aj cez <Context ... <Parameter name="webjetDbname" value="/poolman-local.xml" override="true"/> je mozne zadat cestu
		String customPoolmanPath = null;
		if (Tools.isNotEmpty(systemIwcmDBName) && systemIwcmDBName.endsWith(".xml")) {
			customPoolmanPath = systemIwcmDBName;
			systemIwcmDBName = "iwcm";
		}
		String data = readFileContent(customPoolmanPath);

		StringBuilder availableDatabases = null;
		if (data != null && data.contains("poolman"))
		{
			try
			{
				DocumentBuilderFactory b = DocumentBuilderFactory.newInstance();
				// to be compliant, completely disable DOCTYPE declaration:
				b.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
				DocumentBuilder dc = b.newDocumentBuilder();
				ByteArrayInputStream input = new ByteArrayInputStream(data.getBytes());
				Document doc = dc.parse(input);

				if(doc != null)
				{
					Vector<Node> list = XmlUtils.getChildNodesByPath(doc.getDocumentElement(), "/datasource");
					if( list != null )
					{
						ConfigurableDataSource ds;
						for (Node n : list)
						{
							//default values
							driver = "com.mysql.jdbc.Driver";
							username = "user";
							password = "pass";
							url = "jdbc:mysql://localhost/webjet_web?useUnicode=true&characterEncoding=windows-1250";

							minActive = 0;
							maxActive = 50;

							removeAbandonedTimeout = 5*60;

							dbname = XmlUtils.getFirstChildValue(n, "dbname");

							//zadane meno zmenime na iwcm aby ho interne WebJET pouzival
							//riesene kvoli ING kde public node ma iny pripojovaci retazec
							if (Tools.isNotEmpty(systemIwcmDBName) && systemIwcmDBName.equals(dbname))
							{
								Logger.println(DBPool.class, "Changing dbname from "+dbname+" to iwcm");
								dbname = "iwcm";
							}

							driver = XmlUtils.getFirstChildValue(n, "driver");
							url = XmlUtils.getFirstChildValue(n, "url");
							username = XmlUtils.getFirstChildValue(n, "username");
							password = XmlUtils.getFirstChildValue(n, "password");
							minActive = getIntValue(XmlUtils.getFirstChildValue(n, "minimumSize"), minActive);
							maxActive = getIntValue(XmlUtils.getFirstChildValue(n, "maximumSize"), maxActive);

							autoCommit = Tools.getBooleanValue(XmlUtils.getFirstChildValue(n, "autoCommit"), true);
							readOnly = Tools.getBooleanValue(XmlUtils.getFirstChildValue(n, "readOnly"), false);
							transactionIsolation = XmlUtils.getFirstChildValue(n, "transactionIsolation");

							removeAbandonedTimeout = getIntValue(XmlUtils.getFirstChildValue(n, "connectionTimeout"), removeAbandonedTimeout);

							Logger.println(this,"DATA SET from XML ["+dbname+"], maxActive="+maxActive);

							/**
							 * ak su premenne pre db nastavene ako parametre jvm/env, pouzi tie
							 * pre ine ako iwcm spojenie ma meno suffix _dbname, cize napr. -DwebjetDbUserName_ip_data_jpa
							 * -DwebjetDbUserName
							 * -DwebjetDbPassword
							 *	-DwebjetDbUrl
							 *	-DwebjetDbMinimumSize
							 *	-DwebjetDbMaximumSize
							*/
							String envSuffix = "";
							if ("iwcm".equals(dbname)==false) envSuffix = "_"+dbname;
							if(isSystemPropertySet("webjetDbDriver"+envSuffix))
							{
								driver = getSystemProperty("webjetDbDriver"+envSuffix);
							}
							if(isSystemPropertySet("webjetDbUserName"+envSuffix))
							{
								username = getSystemProperty("webjetDbUserName"+envSuffix);
							}
							if(isSystemPropertySet("webjetDbPassword"+envSuffix))
							{
								password = getSystemProperty("webjetDbPassword"+envSuffix);
							}
							if(isSystemPropertySet("webjetDbUrl"+envSuffix))
							{
								url = getSystemProperty("webjetDbUrl"+envSuffix);
							}
							if(isSystemPropertySet("webjetDbMaximumSize"+envSuffix))
							{
								maxActive = Tools.getIntValue(getSystemProperty("webjetDbMaximumSize"+envSuffix),60);
							}

							if ("com.mysql.jdbc.Driver".equals(driver)) driver = "org.mariadb.jdbc.Driver"; //menime driver pre mysql tak aby sa nemuseli prepisovat vsetky poolmany pri update
							if ("COM.mysql.jdbc.Driver".equals(driver)) driver = "com.mysql.jdbc.Driver"; //ak by blo z nejakeho dovodu treba pouzit mysql driver

							if ("org.mariadb.jdbc.Driver".equals(driver)) {
								url = Tools.replace(url, "jdbc:mysql://", "jdbc:mariadb://");
							}

							if ("iwcm".equals(dbname))
					      	{
								if (driver.indexOf("oracle")!=-1)
								{
									Constants.DB_TYPE = Constants.DB_ORACLE;
									ConfDB.CONF_TABLE_NAME = "webjet_conf";
									ConfDB.CONF_PREPARED_TABLE_NAME = "webjet_conf_prepared";
									ConfDB.MODULES_TABLE_NAME = "webjet_modules";
									ConfDB.ADMINLOG_TABLE_NAME = "webjet_adminlog";
									ConfDB.DB_TABLE_NAME = "webjet_db";
									ConfDB.PROPERTIES_TABLE_NAME = "webjet_properties";
								}
								else if (driver.indexOf("jtds")!=-1 || driver.indexOf("microsoft.sqlserver")!= -1)
								{
									Constants.DB_TYPE = Constants.DB_MSSQL;
								}
								else if (driver.indexOf("postgresql")!=-1 || driver.indexOf("pgsql")!= -1)
								{
									Constants.DB_TYPE = Constants.DB_PGSQL;
								}
								else
								{
									Constants.DB_TYPE = Constants.DB_MYSQL;
								}
								//minimumSize = System.getProperty("webjetDbMinimumSize"); //toto sa nikde nepouziva
							}

							if (password == null) password = "";
							password = decryptPassword(password);

							HikariConfig hc = new HikariConfig();
							hc.setLeakDetectionThreshold(removeAbandonedTimeout*1000l);
							hc.setAutoCommit(autoCommit);
							hc.setReadOnly(readOnly);
							if(Tools.isNotEmpty(transactionIsolation)) {
								hc.setTransactionIsolation(transactionIsolation);
							}
							if((hc.isAutoCommit() || hc.isReadOnly()) && Tools.isEmpty(transactionIsolation)) {
								hc.setTransactionIsolation(""+Connection.TRANSACTION_READ_COMMITTED);
							}

							//for jdbc4 drivers we use isValid(), for older drivers we use testQuery
							String testQuery = XmlUtils.getFirstChildValue(n, "testQuery");
							if (Tools.isEmpty(testQuery) || "true".equals(testQuery)) {
								testQuery = "SELECT 1";
								if (driver.contains("oracle")) testQuery = "SELECT 1 FROM dual";
							}
							if (testQuery != null && testQuery.length()>1) {
								Logger.println(DBPool.class, "HikariCP testQuery: "+testQuery+", driver:"+driver);
								hc.setConnectionTestQuery(testQuery);
								hc.setConnectionInitSql(testQuery);
							}

							Logger.println(DBPool.class, "HikariCP minPoolSize: " + minActive);
							hc.setMinimumIdle(minActive);
							Logger.println(DBPool.class, "HikariCP maxPoolSize: " + maxActive);
							hc.setMaximumPoolSize(maxActive);
							Constants.setInt("webjetDbMaximumSize", maxActive);

							if (driver.contains("oracle")) {
								Logger.println(DBPool.class, "HikariCP SetBigStringTryClob=" + true);
								hc.addDataSourceProperty("SetBigStringTryClob", "true");
							}

							hc.setDriverClassName(driver);
							hc.setUsername(username);
							hc.setPassword(password);
							hc.setJdbcUrl(url);

							String hikariProperties = XmlUtils.getFirstChildValue(n, "hikariProperties");
							if (Tools.isNotEmpty(hikariProperties)) {
								//convert String to properties, construct HikariConfig from it ant use to construct HikariDataSource
								Properties props = new Properties();
								try {
									Logger.println(DBPool.class, "HikariCP properties=" + hikariProperties);
									props.load(new StringReader(hikariProperties.trim()));
									PropertyElf.setTargetFromProperties(hc, props);
								} catch (IOException e) {
									Logger.error(e);
								}
							}

							HikariDataSource hs = new HikariDataSource(hc);
							ds = new WebJetHikariDataSource(hs);
							dataSourcesTable.put(dbname, ds);

							Logger.println(DBPool.class, "Initialized Datasource " + dbname + " url=" + url);

							if (availableDatabases == null)
							{
								availableDatabases = new StringBuilder(dbname);
							}
							else
							{
								availableDatabases.append(',').append(dbname);
							}

						}
					}
				}
			}
			catch (SAXParseException ex) {
				Logger.error(DBPool.class, customPoolmanPath+" is not valid XML file");
			}
			catch (Exception ex)
			{
				Logger.error(ex);
			}
		}

		if (availableDatabases != null)
		{
			Constants.setString("availableDatabases", availableDatabases.toString());
		}

		addExternalDataSources();
	}

	private boolean isSystemPropertySet(String name) {
		String value = System.getProperty(name);
		if (Tools.isNotEmpty(value)) return true;
		value = System.getenv(name);
		if (Tools.isNotEmpty(value)) return true;
		return false;
	}

	private String getSystemProperty(String name) {
		String value = System.getProperty(name);
		if (Tools.isNotEmpty(value)) return value;
		value = System.getenv(name);
		if (Tools.isNotEmpty(value)) return value;
		return "";
	}



    /**
     *
     * @param destroyInstance - ak je true predpoklada sa reinicializacia DBPoolu, inak sa pouziva false, kedy sa vsetko len ukonci (vypnutie servera)
     */
	public synchronized void destroy(boolean destroyInstance)
	{
		Logger.println(this,"DBPool["+Constants.getInstallName()+"] destroy");

		Enumeration<String> keys = dataSourcesTable.keys();
		String key;
		while (keys.hasMoreElements())
		{
			key = keys.nextElement();
			ConfigurableDataSource ds = dataSourcesTable.get(key);
			Logger.println(this,"   Active:" + ds.getNumActive()+" Idle:"+ds.getNumIdle());
			try
			{
				ds.destroy();
			}
			catch (SQLException e)
			{
				Logger.error(e);
			}
		}

		try
		{
		    if (destroyInstance==false)
            {
                for (Enumeration<Driver> e = DriverManager.getDrivers(); e.hasMoreElements(); )
                {
                    Driver driver = e.nextElement();
                    if (driver != null && driver.getClass().getClassLoader() == getClass().getClassLoader())
                    {
                        Logger.println(DBPool.class, "Unloading driver " + driver.toString());
                        DriverManager.deregisterDriver(driver);
                    }
                }
            }
		}
		catch (Exception e)
		{
			System.err.println("Failled to cleanup ClassLoader for webapp " + e.getMessage()); //NOSONAR
			Logger.error(e);
		}

		if (destroyInstance) instance = null;
	}

	public void logConnections()
	{
		Enumeration<String> keys = dataSourcesTable.keys();
		String key;
		while (keys.hasMoreElements())
		{
			key = keys.nextElement();
			try
			{
				ConfigurableDataSource ds = dataSourcesTable.get(key);
				Logger.debug(this,"DBPool["+key+"] active: " + ds.getNumActive()+" idle="+ds.getNumIdle());
			}
			catch (Exception e)
			{
				Logger.error(e);
			}
		}
	}

	/**
	 * Ziskanie dataSource z Hashtabulky
	 * @param dbName
	 * @return
	 */
	public DataSource getDataSource(String dbName)
	{
		if (dataSourcesTable == null)
		{
			initialize();
		}

		DataSource ds = dataSourcesTable.get(dbName);
		//Logger.println(DBPool.class, "getDataSource 1, ds="+ds);

		if(ds==null)
			ds = externalDataSources.get(dbName);

		//Logger.println(DBPool.class, "getDataSource 2, ds="+ds+" TYPE="+Constants.DB_TYPE+" conf table="+ConfDB.CONF_TABLE_NAME);

		return ds;
	}

	/**
	 * Ziskanie WebJETAbandonedDataSource z Hashtabulky
	 * @param dbName
	 * @return
	 */
	public DataSource getWebJETAbandonedDataSource(String dbName)
	{
		return getDataSource(dbName);
	}


	/**
	 * Vrati DB spojenie do databazy WebJETu
	 * @return
	 */
	public static Connection getConnection()
	{
		return(getConnection("iwcm", false));
	}

	/**
	 * Vrati DB spojenie do databazy WebJETu v rezime Connection.TRANSACTION_READ_UNCOMMITTED
	 * @return
	 */
	public static Connection getConnectionReadUncommited()
	{
		return(getConnection("iwcm", true));
	}

	public static Connection getConnection(HttpServletRequest request)
	{
		return(getConnection(getDBName(request)));
	}

	/**
	 * Vrati DB spojenie so zadanym nazvom
	 * @param dbName
	 * @return
	 */
	public static Connection getConnection(String dbName)
	{
		return getConnection(dbName, false);
	}

	/**
	 * Vrati DB spojenie so zadanym nazvom v rezime Connection.TRANSACTION_READ_UNCOMMITTED
	 * @param dbName
	 * @return
	 */
	public static Connection getConnectionReadUncommited(String dbName)
	{
		return getConnection(dbName, false);
	}

	/**
	 * Vrati DB spojenie so zadanym nazvom, ak je nastavena hodnota readUncomitted na true vrati DB spojenie v rezime Connection.TRANSACTION_READ_UNCOMMITTED
	 * @param dbName
	 * @param readUncomitted
	 * @return
	 */
	private static Connection getConnection(String dbName, boolean readUncomitted)
	{
		if (dbName == null)
		{
			dbName = "iwcm";
		}
		if (dbName.indexOf('.')!=-1)
		{
			//asi to dalo cele meno servera, osetri
			dbName = getDBName(dbName);
		}

		//skus to cez Data Source
		DataSource ds = null;
		try
		{
			Connection con = null;

			ds = DBPool.getInstance().getDataSource(dbName);


			if (ds==null) return(null);
			con = ds.getConnection();
			con.setAutoCommit(true);

			if (readUncomitted) setTransactionIsolationReadUNCommited(con);
			else setTransactionIsolationReadCommited(con);

			return(con);
		}
		catch (Exception ex)
		{
			Logger.error(DBPool.class,"CHYBA!!: nepodarilo sa ziskat connection do DB.");
			if(ex.getMessage() != null && ex.getMessage().contains("Cannot get a connection, pool error Timeout waiting for idle object") && ds != null)
			{
				 try
				 {
					  ConfigurableDataSource cds = (ConfigurableDataSource) ds;
					  Logger.println(DBPool.class, "  Active: " + cds.getNumActive() + " idle=" + cds.getNumIdle());
					  if (cds.getNumActive() == Constants.getInt("webjetDbMaximumSize"))
					  {
							if(hasPrintedStackTrace.compareAndSet(false, true))
							{
								RequestBean rb = SetCharacterEncodingFilter.getCurrentRequestBean();
								if(rb != null && Tools.isNotEmpty(rb.getDomain()))
								{
									AdminlogNotifyManager.sendNotification(Adminlog.TYPE_SQLERROR, rb, DB.getDbTimestamp(Tools.getNow()),
															"Na domene " + rb.getDomain() + (ClusterDB.isServerRunningInClusterMode() ? ", node: "+Constants.getString("clusterMyNodeName") : "") +
															" bol pravdepodobne dosiahnuty maximalny pocet DB spojeni pre dbName: "+dbName+". " +
															"Treba preverit ci nenastavaju DB leaky, alebo ci netreba zvysit maximumSize alebo nastavit korektne socketTimeout v poolman.xml.", false);
								}
							}
					  }
				 }
				 catch (Exception ex2)
				 {
					  Logger.error(ex2);
				 }
			}
			Logger.error(ex);
		}

		//no a teraz je to uplne v prdeli...
		return(null);
	}

	@SuppressWarnings("unused")
	private static void logCaller()
	{
		Throwable trace = new Exception().fillInStackTrace();
		//0 - this, 1 - getConnection, 2 - getConnection, 3 - caller
		if (trace.getStackTrace().length > 3)
		{
			String message = String.format("%s.%s() requested DB connection", trace.getStackTrace()[3].getClassName(), trace.getStackTrace()[3].getMethodName()
			);
			Logger.debug(DBPool.class, message);
		}
	}

	public static void setTransactionIsolationReadCommited(Connection dbConn)
	{
		if (Constants.DB_TYPE==Constants.DB_ORACLE) return;

		try
		{
			if (dbConn.getTransactionIsolation()!=Connection.TRANSACTION_READ_COMMITTED)
			{
				dbConn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
			}
		}
		catch (Exception ex)
		{
			Logger.error(ex);
		}
	}

	public static void setTransactionIsolationReadUNCommited(Connection dbConn)
	{
		if (Constants.DB_TYPE==Constants.DB_ORACLE) return;

		try
		{
			if (dbConn.getTransactionIsolation()!=Connection.TRANSACTION_READ_UNCOMMITTED)
			{
				dbConn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
			}
		}
		catch (Exception ex)
		{
			Logger.error(ex);
		}
	}

	/**
	 * @de3precated - DBName nie je potrebne
	 * @param request
	 * @return
	 */
	public static String getDBName(HttpServletRequest request)
	{
		if (request == null)
		{
			return("iwcm");
		}
		return(getDBName(Tools.getServerName(request)));
	}

	/**
	 * @de3precated - DBName nie je potrebne
	 * @param domain
	 * @return
	 */
	public static String getDBName(String domain)
	{
		return("iwcm");
	}

	public static String readFileContent()
	{
		return readFileContent(null);
	}

	public static String readFileContent(String customPoolmanPath)
	{
		StringBuilder contextFile = new StringBuilder();

		String poolmanPath = System.getProperty("webjetPoolmanPath");

		try
		{
			InputStream is;

			if (Tools.isNotEmpty(customPoolmanPath)) {
				poolmanPath = customPoolmanPath;
			}

			if (poolmanPath==null || poolmanPath.length()<1)
			{
				is = DBPool.class.getResourceAsStream("/poolman.xml");
				poolmanPath = "poolman.xml";
			}
			else if (new File(poolmanPath).exists())
			{
				//je to priamo cesta k suboru
				is = new FileInputStream(poolmanPath);
			}
			else
			{
				//je to cesta v classes adresari (napr. poolman-devel.xml)
				is = DBPool.class.getResourceAsStream(poolmanPath);
			}
			if (is == null) {
				//ak sa nenasiel, skus este raz /poolman.xml
				is = DBPool.class.getResourceAsStream("/poolman.xml");
				poolmanPath = "poolman.xml";
			}
			if (is == null) {
				//skus natvrdo /WEB-INF/classes/poolman.xml
				String data = FileTools.readFileContent("/WEB-INF/classes/poolman.xml");
				if (data != null && data.length()>30) contextFile.append(data);
			}

			if (contextFile.length()>30) {
				//it's allready readed
			}
			else if (is != null) {
				InputStreamReader isr = new InputStreamReader(is);
				char[] buff = new char[8000];
				int len;
				String line;
				while ((len = isr.read(buff))!=-1)
				{
					line = new String(buff, 0, len);
					contextFile.append(line);
				}
				isr.close();
				is.close();
			} else {
				Logger.error(DBPool.class, poolmanPath+" file doesn't exists");
			}
		}
		catch (Exception ex)
		{
			Logger.error(ex);
		}

		Runtime rt = Runtime.getRuntime();
	   long free = rt.freeMemory();
	   long total = rt.totalMemory();
	   long used = total - free;
	   long max = rt.maxMemory();
	   int proces = rt.availableProcessors();
	   Logger.println(DBPool.class,"Mem Free   = "+(free/1024/1024) +" MB ("+free +" bytes)<br>");
	   Logger.println(DBPool.class,"Mem Total  = "+(total/1024/1024)+" MB ("+total+" bytes)<br>");
	   Logger.println(DBPool.class,"Mem Used   = "+(used/1024/1024)+" MB ("+used+" bytes)<br>");
	   Logger.println(DBPool.class,"Mem Max    = "+(max/1024/1024)+" MB ("+max+" bytes)<br>");
	   Logger.println(DBPool.class,"Processors = "+proces);

		//Logger.println(this,"xml="+contextFile);

   		return(contextFile.toString());
	}

	private static int getIntValue(String value, int defaultValue)
	{
		int ret = defaultValue;
		try
		{
			if (value!=null)
			{
				ret = Integer.parseInt(value.trim());
			}
		}
		catch (Exception ex)
		{

		}
		return(ret);
	}

	/**
	 * Vrati nazvy DataSource-ov z hashtabulky
	 * @return
	 */
	public static Set<String> getDataSourceNames()
	{
		Set<String> dataSourceNames = new HashSet<>();
		dataSourceNames.addAll(dataSourcesTable.keySet());
		if (externalDataSources != null) dataSourceNames.addAll(externalDataSources.keySet());
		return dataSourceNames;
	}

	/**
	 * Inicializacia JPA (vola sa z InitServlet-u), bezdovodne NEVOLAT, inak sa entityManagerFactories odznova inicializuju!!!
	 */
	public static void jpaInitialize()
	{
		DebugTimer dt = new DebugTimer("DBPool JPA init");
		entityManagerFactories = new HashMap<>();

		for(String name : getDataSourceNames())
		{
			if (JpaTools.isJPADatasource(name))
			{
				dt.diff("START <:--:> "+name);

				EntityManagerFactory factory = new WebJETPersistenceProvider().createEntityManagerFactory(name, null);

				entityManagerFactories.put(name, factory);
				dt.diff("END <:--:> "+name+" factory="+factory);
			}
		}
		dt.diff("-----JPA INIT E N D-------");
	}

	/**
	 * zatvorenie entityManagerFactories (vola sa v destroy-i InitServlet-u)
	 */
	public static void jpaDestroy()
	{
	    if (entityManagerFactories==null) return;
		for (EntityManagerFactory factory : entityManagerFactories.values())
		{
			if (factory.isOpen())
				factory.close();
		}
	}

	/**
	 * vrati EntityManagerFactory pre zadany nazov DB spojenia
	 * @param dbName - Nazov DB spojenia
	 * @return
	 */
	public static EntityManagerFactory getEntityManagerFactory(String dbName)
	{
		return entityManagerFactories.get(dbName);
	}

	/**
	 * naplni mapu "externalDataSources" datasourcami z aplikacneho servera, ktorych nazov obsahuje "webjet"
	 */
	private static void addExternalDataSources()
	{
		Logger.println(DBPool.class, "Adding external datasources");

		Map<String, DataSource> allDataSources = new HashMap<>();
		Map<String, DataSource> wjDataSources = new HashMap<>();

		try
		{
			addAllExternalDatasources(new InitialContext(), allDataSources, "");

			for(Entry<String, DataSource> entry : allDataSources.entrySet())
			{
				if(entry.getKey().contains("/webjet/"))
				{
					String dsName = entry.getKey().substring(entry.getKey().lastIndexOf("/") + 1);
					Logger.println(DBPool.class, "Adding JNDI datasource "+entry.getKey()+" dsName="+dsName);
					//kluc v mape je nazov za poslednim lomitkom /
					DataSource ds = entry.getValue();
					wjDataSources.put(dsName, ds);
					if ("iwcm".equals(dsName))
					{
						//nastav driver test
						//Logger.debug(DBPool.class, ds.toString());
						Connection conn = ds.getConnection();
						String databaseName = conn.getMetaData().getDatabaseProductName();

						Logger.println(DBPool.class, "Database name: "+databaseName);

						if (Tools.isNotEmpty(databaseName))
						{
							databaseName = databaseName.toLowerCase();
							if (databaseName.indexOf("oracle")!=-1 || entry.getKey().contains("/oracle/"))
							{
								Logger.println(DBPool.class, "Setting ORACLE dialect - "+databaseName);
								Constants.DB_TYPE = Constants.DB_ORACLE;
								ConfDB.CONF_TABLE_NAME = "webjet_conf";
					      		ConfDB.CONF_PREPARED_TABLE_NAME = "webjet_conf_prepared";
					      		ConfDB.MODULES_TABLE_NAME = "webjet_modules";
					      		ConfDB.ADMINLOG_TABLE_NAME = "webjet_adminlog";
					      		ConfDB.DB_TABLE_NAME = "webjet_db";
					      		ConfDB.PROPERTIES_TABLE_NAME = "webjet_properties";
							}
							if (databaseName.indexOf("microsoft")!=-1 || entry.getKey().contains("/mssql/"))
							{
								Logger.println(DBPool.class, "Setting MS SQL dialect - "+databaseName);
								Constants.DB_TYPE = Constants.DB_MSSQL;
							}
						}
						conn.close();
					}
				}
			}
		} catch(Exception e) {
            Logger.error(e);
		}

		externalDataSources = wjDataSources;
	}

	/**
	 * ziska vsetky datasourcy z aplikacneho servera a prida ich do mapy
	 * @param ctx
	 * @param map
	 * @param fullPath
	 */
	private static void addAllExternalDatasources(Context ctx, Map<String, DataSource> map, String fullPath)
	{
		try
		{
			String defaultJndiPath = "/jndi/webjet/iwcm"; //NOSONAR

			DataSource dataSource = (DataSource)ctx.lookup(defaultJndiPath);
			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources - "+defaultJndiPath+", defaultDataSource="+dataSource);
			if (dataSource != null)
			{
				map.put(defaultJndiPath, dataSource);
			}
		}
		catch (javax.naming.NameNotFoundException e)
		{
			//do nothing
		}
		catch (Exception e)
		{
			Logger.debug(DBPool.class, e.getMessage());
			//sk.iway.iwcm.Logger.error(e);
		}

		try
		{
			String defaultJndiPath = "/jdbc/webjet/iwcm"; //NOSONAR

			DataSource dataSource = (DataSource)ctx.lookup(defaultJndiPath);
			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources - "+defaultJndiPath+", defaultDataSource="+dataSource);
			if (dataSource != null)
			{
				map.put(defaultJndiPath, dataSource);
			}
		}
		catch (javax.naming.NameNotFoundException e)
		{
			//do nothing
		}
		catch (Exception e)
		{
			Logger.debug(DBPool.class, e.getMessage());
			//sk.iway.iwcm.Logger.error(e);
		}

		try
		{
			String defaultJndiPath = "java:comp/env/jdbc/webjet/iwcm";

			DataSource dataSource = (DataSource)ctx.lookup(defaultJndiPath);
			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources - "+defaultJndiPath+", defaultDataSource="+dataSource);
			if (dataSource != null)
			{
				map.put(defaultJndiPath, dataSource);
			}
		}
		catch (javax.naming.NameNotFoundException e)
		{
			//do nothing
		}
		catch (Exception e)
		{
			Logger.debug(DBPool.class, e.getMessage());
			//sk.iway.iwcm.Logger.error(e);
		}

		try
		{
			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources");

			String namespace = ctx instanceof InitialContext ? ctx.getNameInNamespace() : "";

			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources, namespace2="+namespace);

			NamingEnumeration<NameClassPair> list = ctx.list(namespace);

			//Object dataSource = ctx.lookup("java:comp/env/jndi/webjet/iwcm");
			//Logger.println(DBPool.class, "DBPool: addAllExternalDatasources, dataSource="+dataSource);

			Logger.println(DBPool.class, "DBPool: addAllExternalDatasources, list="+list);
			while (list.hasMoreElements())
			{
				try
				{
					NameClassPair next = list.next();
					String name = next.getName();
					String jndiPath = namespace + name;
					Logger.println(DBPool.class, "Scanning "+jndiPath);

					//toto na Tomcate scanovalo vsetky subory
					if ("Resources".equals(jndiPath)) continue;
					try
					{
						Object tmp = ctx.lookup(jndiPath);
						if (tmp instanceof Context)
						{
							addAllExternalDatasources((Context) tmp, map, fullPath+"/"+jndiPath);
						}
						else if(tmp instanceof DataSource)
						{
							map.put(fullPath+"/"+jndiPath, (DataSource) tmp);
						}
					}
					catch (Exception e){}
				} catch(Exception e) {
                    Logger.error(e);
				}
			}
		}
		catch (javax.naming.NameNotFoundException e)
		{
			//do nothing
		}
		catch(Exception e)
		{
			Logger.debug(DBPool.class, e.getMessage());
			//sk.iway.iwcm.Logger.error(e);
		}
	}

	 /**
	  * Cez reflection zisti, ci existuje trieda na desifrovanie, ak ano pokusi sa desifrovat heslo. Ak nie vrati povodne heslo
	  * Ticket: #19322 - NN IT - sifrovanie hesiel DB
	  * @param encryptedPassword - "cesta k triede s metodou decrypt"-"sifrovane_heslo" napr: sk.iway.nn.system.CustomPasswordDecryption-1DV4hO5IVqOXpMahxGJ8iuEBN/dTSiQXGXEBZABAr0E=
	  * @author	$(prau)
	  */
	 private static String decryptPassword(String encryptedPassword)
	 {
		  if(Tools.isNotEmpty(encryptedPassword) && encryptedPassword.startsWith("sk.iway.") && encryptedPassword.contains("-"))
		  {
				try
				{
					 Class<?> c = Class.forName(encryptedPassword.substring(0, encryptedPassword.indexOf("-")));
					 Object o = c.getDeclaredConstructor().newInstance();
					 Method m;
					 Class<?>[] parameterTypes = new Class[] {String.class};
					 Object[] arguments = new Object[] {encryptedPassword.substring(encryptedPassword.indexOf("-")+1)};
					 m = c.getMethod("decrypt", parameterTypes);
					 String decryptedPassword = (String)m.invoke(o, arguments);
					 if(Tools.isNotEmpty(decryptedPassword))
					 {
						  Logger.debug(DBPool.class, "Decrypted password used.");
						  return decryptedPassword;
					 }
				}
				catch (Exception ex)
				{
					 Logger.error(ex);
					 Logger.debug(DBPool.class, "Default password used.");
				}
		  }
		  return encryptedPassword;
	 }
}