/*******************************************************************************
* Copyright (c) 2012-2015 INRIA.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Generoso Pagano - initial API and implementation
******************************************************************************/
package fr.inria.soctrace.lib.storage;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.inria.soctrace.lib.model.IModelElement;
import fr.inria.soctrace.lib.model.utils.SoCTraceException;
import fr.inria.soctrace.lib.storage.dbmanager.DBManager;
import fr.inria.soctrace.lib.storage.dbmanager.MySqlDBManager;
import fr.inria.soctrace.lib.storage.dbmanager.SQLiteDBManager;
import fr.inria.soctrace.lib.storage.utils.SQLConstants.FramesocTable;
import fr.inria.soctrace.lib.storage.visitors.ModelVisitor;
import fr.inria.soctrace.lib.utils.Configuration;
import fr.inria.soctrace.lib.utils.DBMS;
import fr.inria.soctrace.lib.utils.Configuration.SoCTraceProperty;
/**
* Base abstract class for DB Objects.
* It provides basic operations common to all DBs.
*
* @author "Generoso Pagano <generoso.pagano@inria.fr>"
*/
public abstract class DBObject {
/**
* Logger
*/
private static final Logger logger = LoggerFactory.getLogger(DBObject.class);
protected DBManager dbManager = null;
protected ModelVisitor saveVisitor = null;
protected ModelVisitor deleteVisitor = null;
protected ModelVisitor updateVisitor = null;
private boolean visitorsInitialized = false;
/**
* DB mode
*/
public static enum DBMode {
DB_CREATE, /** Create the DB */
DB_OPEN /** Open an existing DB */
}
/**
* Constructor
* @param name DB name
* @param mode DB object creation mode
* @throws SoCTraceException
*/
public DBObject(String name, DBMode mode) throws SoCTraceException
{
dbManager = getDBManager(name);
// create or open the database
switch(mode) {
case DB_CREATE:
createDB();
break;
case DB_OPEN:
openDB();
break;
default:
break;
}
logger.debug("Create DBObject: {}, {}", name, mode.name());
}
/**
* Check if a database with a given name exists.
*
* @param name database name
* @return true if the database exists
* @throws SoCTraceException
*/
public static boolean isDBExisting(String name) throws SoCTraceException {
DBManager dbm = getDBManager(name);
return dbm.isDBExisting();
}
/**
* Check if the settings for a database are correct
*
* @param name
* database name
* @return true if the settings are correct
* @throws SoCTraceException
*/
public static boolean checkSettings(String name) throws SoCTraceException {
DBManager dbm = getDBManager(name);
return dbm.checkSettings();
}
/**
* Close the database.
* To be used in a finally block.
*
* @param dbObj database object
*/
public static void finalClose(DBObject dbObj) {
if (dbObj != null) {
try {
dbObj.close();
} catch (SoCTraceException e) {
logger.error("Error closing the DB object {}", dbObj.getDBName());
e.printStackTrace();
}
}
}
/**
* Create a new database.
* It creates a SystemDB or a TraceDB according to the
* actual concrete class.
* @throws SoCTraceException
*/
protected abstract void createDB() throws SoCTraceException;
/**
* Open an existing database.
* It opens a SystemDB or a TraceDB according to the
* actual concrete class.
* @throws SoCTraceException
*/
protected abstract void openDB() throws SoCTraceException;
/**
* Clean cache structures, if any.
*/
protected abstract void cleanCache();
/**
* Commit current database transaction.
* @throws SoCTraceException
*/
public synchronized void commit() throws SoCTraceException {
executeVisitorBatches();
clearVisitorBatches();
try {
if(!dbManager.getConnection().isClosed()) {
dbManager.getConnection().commit();
}
} catch (SQLException e) {
throw new SoCTraceException(e);
}
}
/**
* Rollback current database transaction.
* @throws SoCTraceException
*/
public synchronized void rollback() throws SoCTraceException {
clearVisitorBatches();
try {
if(!dbManager.getConnection().isClosed()) {
dbManager.getConnection().rollback();
}
} catch (SQLException e) {
throw new SoCTraceException(e);
}
}
/**
* Commit, close visitors, clean cache structures
* and close connection.
* @throws SoCTraceException
*/
public synchronized void close() throws SoCTraceException {
commit();
closeVisitors();
cleanCache();
dbManager.closeConnection();
logger.debug("Close DBObject: {}", dbManager.getDBName());
}
/**
* Drop the actual database and automatically close the DB object.
* @throws SoCTraceException
*/
public synchronized void dropDatabase() throws SoCTraceException {
dbManager.dropDB();
}
/**
* Connection getter. It opens the connection if closed.
* @return The DB connection
* @throws SoCTraceException
*/
public Connection getConnection() throws SoCTraceException {
return dbManager.getConnection();
}
/**
* DB name getter
* @return The DB name.
*/
public String getDBName() {
return dbManager.getDBName();
}
/**
* Get table info query
*
* @param framesocTable
* the name of the table
* @return the query
*/
public String getTraceInfoQuery(FramesocTable framesocTable) {
return dbManager.getTableInfoQuery(framesocTable);
}
/**
* Check if the DB related to this DBObject is already existing.
*
* @return true if this DB is already existing, false otherwise
* @throws SoCTraceException
*/
protected boolean isThisDBExisting() throws SoCTraceException {
return dbManager.isDBExisting();
}
/**
* Get the first available sequential id from the DB for
* a given table. Use it if you have to insert just one row.
*
* @param tableName name of the table
* @param idColumnName name of the id column
* @return a new id
* @throws SoCTraceException
*/
public synchronized int getNewId(String tableName, String idColumnName) throws SoCTraceException {
try {
Statement stm = dbManager.getConnection().createStatement();
ResultSet rs = stm.executeQuery("SELECT "+ idColumnName +" FROM " + tableName + " ORDER BY " + idColumnName);
int expectedNext = 0;
while (rs.next()) {
int id = rs.getInt(idColumnName);
if (id > expectedNext)
break;
expectedNext++;
}
stm.close();
return expectedNext;
} catch (SQLException e) {
throw new SoCTraceException(e);
}
}
/**
* Get the maximum id from the DB for a given table.
* Use it if you have to insert a sequence.
*
* @param tableName name of the table
* @param idColumnName name of the id column
* @return the max id
* @throws SoCTraceException
*/
public synchronized int getMaxId(String tableName, String idColumnName) throws SoCTraceException {
try {
Statement stm = dbManager.getConnection().createStatement();
ResultSet rs = stm.executeQuery("SELECT MAX("+ idColumnName +") FROM " + tableName);
int max = 0;
if (rs.next()) {
max = rs.getInt(1);
}
stm.close();
return max;
} catch (SQLException e) {
throw new SoCTraceException(e);
}
}
/**
* Get the number of rows in a given table
* @param tableName name of the table
* @return the count(*) for the table
* @throws SoCTraceException
*/
public synchronized int getCount(String tableName) throws SoCTraceException {
try {
Statement stm = dbManager.getConnection().createStatement();
ResultSet rs = stm.executeQuery("SELECT COUNT(*) FROM " + tableName);
int count = 0;
if (rs.next()) {
count = rs.getInt(1);
}
stm.close();
return count;
} catch (SQLException e) {
throw new SoCTraceException(e);
}
}
/*
* Visitors
*/
/**
* Save an IModelElement into the DB.
*
* @param element element to save
* @throws SoCTraceException
*/
public synchronized void save(IModelElement element) throws SoCTraceException {
if (visitorsInitialized == false) {
initializeVisitors();
visitorsInitialized = true;
}
element.accept(saveVisitor);
}
/**
* Delete an IModelElement into the DB.
*
* @param element element to delete
* @throws SoCTraceException
*/
public synchronized void delete(IModelElement element) throws SoCTraceException {
if (visitorsInitialized == false) {
initializeVisitors();
visitorsInitialized = true;
}
element.accept(deleteVisitor);
}
/**
* Update an IModelElement into the DB.
*
* @param element element to update
* @throws SoCTraceException
*/
public synchronized void update(IModelElement element) throws SoCTraceException {
if (visitorsInitialized == false) {
initializeVisitors();
visitorsInitialized = true;
}
element.accept(updateVisitor);
}
/**
* Execute and clear visitor batches.
* @throws SoCTraceException
*/
public synchronized void flushVisitorBatches() throws SoCTraceException {
executeVisitorBatches();
clearVisitorBatches();
}
/*
* These methods must be called in a synchronized environment
*/
/**
* Initialize the visitors.
*
* @throws SoCTraceException
*/
protected abstract void initializeVisitors() throws SoCTraceException;
/**
* Close visitor batches
* @throws SoCTraceException
*/
private void closeVisitors() throws SoCTraceException {
if (visitorsInitialized) {
saveVisitor.close();
saveVisitor = null;
deleteVisitor.close();
deleteVisitor = null;
updateVisitor.close();
updateVisitor = null;
visitorsInitialized = false;
}
}
/**
* Execute visitor batches.
* @throws SoCTraceException
*/
private void executeVisitorBatches() throws SoCTraceException {
if (visitorsInitialized) {
saveVisitor.executeBatches();
deleteVisitor.executeBatches();
updateVisitor.executeBatches();
}
}
/**
* Clear visitor batches.
* @throws SoCTraceException
*/
private void clearVisitorBatches() throws SoCTraceException {
if (visitorsInitialized) {
saveVisitor.clearBatches();
deleteVisitor.clearBatches();
updateVisitor.clearBatches();
}
}
// utilities
private static DBManager getDBManager(String name) throws SoCTraceException {
// create DB manager and open the connection
String dbms = Configuration.getInstance().get(SoCTraceProperty.soctrace_dbms);
if (dbms.equalsIgnoreCase(DBMS.SQLITE.toString())) {
return new SQLiteDBManager(name);
} else if (dbms.equalsIgnoreCase(DBMS.MYSQL.toString())) {
return new MySqlDBManager(name);
}
throw new SoCTraceException("Unknown DBMS: " + dbms);
}
}