/* * * YAQP - Yet Another QSAR Project: * Machine Learning algorithms designed for the prediction of toxicological * features of chemical compounds become available on the Web. Yaqp is developed * under OpenTox (http://opentox.org) which is an FP7-funded EU research project. * This project was developed at the Automatic Control Lab in the Chemical Engineering * School of National Technical University of Athens. Please read README for more * information. * * Copyright (C) 2009-2010 Pantelis Sopasakis & Charalampos Chomenides * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Contact: * Pantelis Sopasakis * chvng@mail.ntua.gr * Address: Iroon Politechniou St. 9, Zografou, Athens Greece * tel. +30 210 7723236 */ package org.opentox.db.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.Driver; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Properties; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.opentox.config.Configuration; import org.opentox.core.exceptions.YaqpException; import org.opentox.core.interfaces.JProcessor; import org.opentox.core.processors.BatchProcessor; import org.opentox.db.exceptions.DbException; import org.opentox.db.interfaces.JDbConnector; import org.opentox.db.table.StandardTables; import org.opentox.db.table.Table; import org.opentox.db.table.TableCreator; import org.opentox.util.logging.YaqpLogger; import org.opentox.util.logging.levels.*; import static org.opentox.core.exceptions.Cause.*; /** * This Singleton Class manages the connection to the database. * @author Sopasakis Pantelis * @author Charalampos Chomenides */ public class TheDbConnector implements JDbConnector { private static TheDbConnector instanceOfthis = null; /** * Public access point to the {@link TheDbConnector DB Connection Singleton }. * Use this static connection to the database to perform any operations. * The first time this field is retrieved, a new DB connection is started. * * @see TheDbConnector#init() DB initialization. */ public static TheDbConnector DB = getInstance(); /** * SQL connection to the database */ private Connection connection; /** * Database Properties */ private final Properties properties = Configuration.getProperties(); /** * Name of the database. */ private final String database_name = properties.getProperty("database.name", "yaqp/yaqpdb"); /** * Database port */ private final String database_port = properties.getProperty("database.port", "1527"); /** * URL of the database */ private final String database_url = properties.getProperty("database.urlbase", "jdbc:derby://localhost") + ":" + database_port + "/" + database_name; /** * User/Schema of the database */ private final String database_user = properties.getProperty("database.user", "itsme"); /** * Driver used to connect to the database. By default this is * org.apache.derby.jdbc.EmbeddedDriver. Note that derby.jar should * be in thy classpath. */ private final String database_driver = properties.getProperty("database.driver", "org.apache.derby.jdbc.EmbeddedDriver"); /** * Java command. You are adviced to use the SUN variant (version 6 or higher). */ private final String javacmd = properties.getProperty("javaCommand", "java"); /** * Options/Directives to the java virtual machine. */ private final String javaOptions = properties.getProperty("javaOptions", "-Djava.net.preferIPv4Stack=true"); /** * Home directory for DERBY */ private final String derbyHome = properties.getProperty("derbyHome", "/usr/local/sges-v3/javadb"); private static ArrayList<String> TABLE_LIST = null; private final Runtime runtime = Runtime.getRuntime(); /** * Connection Flag. */ private boolean isConnected = false; private boolean isInitialized = false; private final Lock lock = new ReentrantLock(); private synchronized static TheDbConnector getInstance() { if (instanceOfthis == null) { try { instanceOfthis = new TheDbConnector(); } catch (Throwable e) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAE317 - Unable to connect to " + DB.database_url + " :: " + e)); } } return (TheDbConnector) instanceOfthis; } /** * Initialize the Connection to the database. Initializes the standard tables of * the database. Check if the database is initialized usign the method {@link * TheDbConnector#isInitialized() }. If the database connection is already * initialized, no action is taken. * * @throws DbException If the database could not be initialized properly. */ public synchronized static void init() throws DbException { DB.connect(); if (!DB.isInitialized()) { TableCreator creator = new TableCreator(); BatchProcessor<Table, Object, JProcessor<Table, Object>> bp = new BatchProcessor<Table, Object, JProcessor<Table, Object>>(creator, 1, 1); ArrayList<Table> tableToBeCreated = new ArrayList<Table>(); for (StandardTables t : StandardTables.values()) { tableToBeCreated.add(t.getTable()); } try { YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Attempting to create the standard tables...")); bp.process(tableToBeCreated); YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Standard tables were initialized...")); DB.isInitialized = true; // TODO; Check if the tables were really created! } catch (YaqpException ex) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAE318 - " + ex.toString())); } } else { YaqpLogger.LOG.log(new Trace(TheDbConnector.class, "Database is Already Initialized")); } DB.isConnected = true; } /** * Retrieves whether the database connection is properly initialized, i.e. * if there is an established connection and the standard tables where * created. * @return true if the database is initialized */ public boolean isInitialized() { return isInitialized; } /** * Private constructor. * @throws YaqpException If a connection cannot be established. In such a * case, check again your server.properties file. */ private TheDbConnector() throws YaqpException { //connect(); } public Lock getLock() { return lock; } public void connect() { if (!isConnected()) { try { startDerbyServer(); loadDriver(); establishConnection(); } catch (Throwable ex) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAE319 - Unable to connect to " + DB.database_url + " :: " + ex)); throw new RuntimeException(ex); } } } public void disconnect() { if (connection != null) { try { connection.close(); instanceOfthis = null; DB.isConnected = false; connection = null; isInitialized = false; } catch (SQLException ex) { YaqpLogger.LOG.log(new ScrewedUp(TheDbConnector.class, "XAE320 - Connection " + database_url + " cannot close!")); } } } public String getDatabaseName() { return database_name; } public String getDatabaseUrl() { return database_url; } public int getDatabasePort() throws YaqpException { try { return Integer.parseInt(database_port); } catch (NumberFormatException nfe) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAE321 - Not acceptable port :" + database_port)); throw new YaqpException(XDB18, "Wrong port declaration :" + database_port, nfe); } } public String getDatabaseDriver() { return database_driver; } public boolean isConnected() { return isConnected; } public Connection getConnection() { return connection; } public String getDatabaseUser() { return database_user; } public DatabaseMetaData getMetaData() throws YaqpException { if (connection != null && isConnected()) { try { return connection.getMetaData(); } catch (SQLException ex) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAE323 - No connection to the database :" + database_name)); throw new DbException(XDB19, "No connection to the database", ex); } } else { String message = "No connection to the database :" + database_name; YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, message)); throw new DbException(XDB20, message); } } public ArrayList<String> getTableNames() throws YaqpException { if (TABLE_LIST == null) { try { TABLE_LIST = new ArrayList<String>(); DatabaseMetaData md = getMetaData(); String[] rsOps = {"TABLE"}; ResultSet rs = md.getTables(null, null, "%", rsOps); while (rs.next()) { TABLE_LIST.add(rs.getString(3)); } } catch (SQLException ex) { throw new DbException(XDB21, ex); } } return TABLE_LIST; } public void killDerby(){ final String[] derby_kill_command = { javacmd, javaOptions, "-jar", derbyHome + "/lib/derbyrun.jar", "server", "shutdown" }; try { Runtime.getRuntime().exec(derby_kill_command); } catch (IOException ex) { } } public boolean isDerbyRunning() { final String[] derby_ping_command = { javacmd, javaOptions, "-jar", derbyHome + "/lib/derbyrun.jar", "server", "ping", "-p", database_port}; try { Process derby_ping = Runtime.getRuntime().exec(derby_ping_command); BufferedReader br = new BufferedReader(new InputStreamReader(derby_ping.getInputStream())); if (br.readLine().contains("Connection obtained")) { return true; } else { return false; } } catch (IOException ex) { return false; } } /** * Makes one attempt to start the derby server * @throws Exception */ public void startDerbyServer() throws Exception { /** * We tried the following, but encountered some problems. In particular it * was impossible to connect to the database from the console when the * application was connected already! */ // serverControl = new NetworkServerControl(); // try { // serverControl.shutdown(); // } catch (Throwable thr) { // } // serverControl.start(new PrintWriter(new FileOutputStream("derby.log"))); final String[] derby_start_command = { javacmd, javaOptions, "-jar", derbyHome + "/lib/derbyrun.jar", "server", "start", "-p", database_port }; YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Attempting connection to the derby server...")); boolean derby_alive = isDerbyRunning(); if (!derby_alive) { YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Derby server is down. Now starting...")); runtime.exec(derby_start_command); // Wait until the derby server is started. Thread.sleep(400); derby_alive = isDerbyRunning(); } YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Derby server is up, listening and ready" + " to accept connections on port " + getDatabasePort())); } /** * Load the JDBC driver specified in the properties section * * @see Configuration server configuration * @see Configuration#getProperties() current properties * @see Configuration#loadDefaultProperties() default properties */ private void loadDriver() { try { Driver myDriver = (Driver) Class.forName(database_driver).newInstance(); YaqpLogger.LOG.log(new Info(TheDbConnector.class, "The driver " + database_driver + " was successfully loaded.")); assert (myDriver.jdbcCompliant()); } catch (Exception exc) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAF335 - Error while loading the JDBC driver ::" + exc)); } } /** * Establishes a connection with the database or creates a new database * if the specified is not found. */ private void establishConnection() { YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Attempting connection to :" + database_url)); try { connection = DriverManager.getConnection(database_url, database_user, "letmein"); isConnected = true; YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Database Connection established at " + database_url + " by " + database_user + " - Now Connected!")); } catch (SQLException e) { if (e.getErrorCode() == 40000) { YaqpLogger.LOG.log(new Info(TheDbConnector.class, "Database " + database_url + " was not found -- creating...")); createDataBase(); } else { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAG612 - Unexpected condition while connecting to the database :: " + e)); throw new RuntimeException(e); } } catch (Exception e) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAG703 - Unable to connect to " + DB.database_url + "--" + e)); } } /** * This method is called when the specified database does not exist and it is * created. The directive <code>create=true</code> is used within the URL * of the database */ private void createDataBase() { try { connection = DriverManager.getConnection(database_url + ";create=true", database_user, "letmein"); YaqpLogger.LOG.log(new Info(TheDbConnector.class, "New database was generated at " + database_url + " by " + database_user)); if (connection != null) { isConnected = true; } } catch (SQLException ex) { YaqpLogger.LOG.log(new Fatal(TheDbConnector.class, "XAG846 - Encountered an error while trying to " + "create a new database :: " + ex)); } } }