
net.jmatrix.db.schema.DBM Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsql Show documentation
Show all versions of jsql Show documentation
SQL Utilities including simple command line, schema management.
package net.jmatrix.db.schema;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import net.jmatrix.db.common.ClassLogFactory;
import net.jmatrix.db.common.ConnectionInfo;
import net.jmatrix.db.common.DBUtils;
import net.jmatrix.db.common.DebugUtils;
import net.jmatrix.db.common.Version;
import net.jmatrix.db.common.console.SysConsole;
import net.jmatrix.db.common.console.TextConsole;
import net.jmatrix.db.jsql.formatters.PrettyFormatter;
import net.jmatrix.db.schema.action.Action;
import net.jmatrix.db.schema.action.ApplyAction;
import net.jmatrix.db.schema.action.InitAction;
import net.jmatrix.db.schema.action.ReapplyAction;
import net.jmatrix.db.schema.action.RollbackAction;
import net.jmatrix.db.schema.data.v2.DBMData;
import net.jmatrix.db.schema.data.v2.DBMLock;
import org.slf4j.Logger;
/**
* Command line entry point for database management.
*
* Inputs include:
* - connection properties
* - directory of Versions
*
*
*
* Recommended Updates:
* Case 1: checksum of latest version mismatch.
* Latest version must have been APPLY
* Action: reapply version
*
* Case 2: (After case 1) DB version less than DiskVersion
* Action: UpdateAll - applies all versions up to and including current version.
*
* Case 3: DB Version is ahead of disk version (likely a rollback of software)
* - all rollback versions must have been APPLYed. If MANUAL - cannot be rolled back.
* Action: Rollback to specified version - applying rollback scripts in
* reverse order back to the version specified on disk. Confirm checksum.
*
* Other cases:
* - Mid version checksum mismatch - log schema error and continue.
* - File checksum does not match file checksum in DB. File was changed.
* - Last DB Version checksum mismatch
* - Schema was altered out of band.
*/
public class DBM {
static TextConsole console=SysConsole.getConsole();
private static Logger log=ClassLogFactory.getLog();
static final String DBM="DBM";
// the path to the directory containing versions of the schema, and patches
File path=null;
// This is essendially a small DAO managing the SQL for the schema manager.
DBMData dbmdata=null;
ConnectionInfo conInfo=null;
List diskVersions=null;
List dbVersions=null;
int LOCK_TIMEOUT=60000;
public DBM(String driver, String url, String user,
String pass, File p) throws SQLException {
this(new ConnectionInfo(driver, url, user, pass), p);
}
/**
* @throws SQLException */
public DBM(ConnectionInfo ci, File p) throws SQLException {
path=p;
conInfo=ci;
dbmdata=new DBMData(conInfo);
}
public DBMData getDBMData() {
return dbmdata;
}
/** */
public void init() throws IOException, SQLException {
log.debug("DBM.init()");
conInfo.initDefaultConnection();
//initDB();
reloadDiskVersions();
if (!conInfo.isConnected()) {
conInfo.connect();
}
reloadDBVersions();
}
public void destroy() {
log.debug("DBM.destroy()");
if (conInfo != null)
conInfo.close();
}
/**
* @throws SQLException
* @throws IOException */
public void initDB() throws SQLException, IOException {
dbmdata.init();
}
/** */
public void showDBHistory() throws Exception {
List dbversions=dbmdata.getDBVersions();
PrettyFormatter pf=new PrettyFormatter();
String[] fields=new String[] {
"version", "action", "hostname", "hostuser", "dbuser", "rollback", "dbChecksum", "fileChecksum"
};
String s=pf.format(dbversions, fields, console.getColumns());
log.info(s);
log.info("");
log.info("Current DB Version: "+getCurrentDBVersion());
}
public void showDiskVersions() throws Exception {
if (diskVersions == null) {
log.warn("No Schema Versions on Disk at "+path);
return;
}
PrettyFormatter pf=new PrettyFormatter();
String[] fields=new String[] {
"version", "applyCount", "rollbackCount", "checksum"
};
String s=pf.format(diskVersions, fields, console.getColumns());
log.info(s);
log.info("");
log.info("Current DB Version: "+getCurrentDBVersion());
}
public void showVersionStatus() throws NoSuchMethodException,
SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, IOException, SQLException {
reloadDBVersions();
if (diskVersions == null) {
log.warn("No Schema Versions on Disk at "+path);
return;
}
if (dbVersions == null) {
log.warn("no Schema Versions in DB at "+conInfo);
return;
}
Set allVersions=new TreeSet();
for (DiskVersion version:diskVersions) {
allVersions.add(version.getVersion());
}
for (DBVersion version:dbVersions) {
allVersions.add(version.getVersion());
}
List avlist=new ArrayList();
avlist.addAll(allVersions);
Collections.sort(avlist);
//dbmdata.getCurrentVersion();
Version currentDBVer=getCurrentDBVersion();
List vdlist=new ArrayList();
for (Version version:avlist) {
String sver=version.toString();
DBVersion dbv=findLatestDBVersion(sver);
if (dbv != null && dbv.getVersion().compareTo(currentDBVer) > 0) {
dbv=null;
}
DiskVersion dv=findDiskVersion(sver);
VersionDisplay vd=new VersionDisplay(version, dv, dbv);
vdlist.add(vd);
}
PrettyFormatter pf=new PrettyFormatter();
String report=pf.format(vdlist,
new String[] {"version", "applyCount", "rollbackCount",
"dbDate", "action"}, console.getColumns());
log.info(report);
log.info("");
log.info("Current DB Version: "+getCurrentDBVersion());
}
public boolean reloadDBVersions() throws SQLException {
log.debug("Reloading DBM DB Versions from "+conInfo.getUrl());
if (dbmdata.isV2Schema()) {
dbVersions=dbmdata.getDBVersions();
return true;
} else {
log.warn("DBM Schema not available or out date.");
}
return false;
}
/**
* @throws IOException */
public List reloadDiskVersions() throws IOException {
log.debug("Reloading DBM Disk Versions from "+path);
if (path == null)
return null;
File vdirs[]=path.listFiles(new FileFilter() {
@Override
public boolean accept(File path) {
if (path.isDirectory())
return true;
return false;
}
});
diskVersions=new ArrayList();
for (File vdir:vdirs) {
diskVersions.add(new DiskVersion(vdir));
}
Collections.sort(diskVersions);
return diskVersions;
}
public Version getMaxDiskVersion() throws IOException {
List versions=reloadDiskVersions();
if (versions != null) {
return versions.get(versions.size()-1).getVersion();
}
return null;
}
/** Manually sets the version to some value. This can be helpful
* when starting to manage a schema 'in flight'
* @throws IOException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchMethodException */
public void setVersion(String ver) throws SQLException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
log.info("Attempting to set DB version to '"+ver+"'");
// make suere the version is valid.
Version version=new Version(ver);
DBMLock lock=null;
try {
lock=dbmdata.acquireLock(DBM, LOCK_TIMEOUT);
dbmdata.setVersion(version.toString(), "MANUAL", null, false, null, null);
} catch (Exception ex) {
log.error("Error setting DB Version", ex);
} finally {
dbmdata.releaseLock(lock);
}
showVersionStatus();
}
/** */
public void reapply(String version) throws SQLException, InterruptedException {
DiskVersion diskVer=findDiskVersion(version);
Action action=new ReapplyAction(this, diskVer);
executeActionWithLock(action);
}
/** */
public void rollback(String version) throws SQLException, IOException, InterruptedException {
DiskVersion diskVer=findDiskVersion(version);
Action action=new RollbackAction(this, diskVer);
executeActionWithLock(action);
}
/** */
public void apply(String version) throws SQLException, IOException, InterruptedException {
DiskVersion diskVer=findDiskVersion(version);
Action action=new ApplyAction(this, diskVer);
executeActionWithLock(action);
}
private DiskVersion findDiskVersion(String ver) {
DiskVersion diskVer=null;
for (DiskVersion diskVersion:diskVersions) {
if (diskVersion.getVersion().toString().equals(ver)) {
diskVer=diskVersion;
break;
}
}
return diskVer;
}
/**
* In the case of reapplication of specific versions, it is possible
* for the DB to have the same version number state a various times
* in history.
*
* This returns the most recent time that the DB had the queried version.
*/
private DBVersion findLatestDBVersion(String ver) {
DBVersion dbv=null;
for (DBVersion v:dbVersions) {
if (v.getVersion().toString().equals(ver))
dbv=v;
}
return dbv;
}
public List recommendUpdateActions() throws IOException, SQLException {
// First, get current version on disk, and current version in DB.
List actions=new ArrayList();
Version dbVersionNum=getCurrentDBVersion(); // could be -1 if no versions in db, which should not happen
Version diskVersionNum=getMaxDiskVersion(); // could be null if no path specified.
if (diskVersionNum == null) {
log.info("DiskVersions are null. Cannot recommend update.");
return null;
}
if (dbVersionNum == null) {
log.warn("DBM DB Version is null - schema not initialized or out of date. Recommend: init.");
dbVersionNum=new Version("-1");
actions.add(new InitAction(this));
}
DiskVersion diskVersion=findDiskVersion(diskVersionNum.toString());
DBVersion dbVersion=null;
if (dbVersionNum.equals(new Version("-1"))) {
} else {
dbVersion=findLatestDBVersion(dbVersionNum.toString());
}
if (diskVersionNum.equals(dbVersionNum)) {
// Compare Checksums, recommend reapply if necessary
if (!diskVersion.getChecksum().equals(dbVersion.getFileChecksum())) {
log.warn("Disk Version "+diskVersion.getVersion()+" checksum("+
diskVersion.getChecksum()+") does not match DB Version "+dbVersion.getVersion()+
" checksum("+dbVersion.getFileChecksum()+")");
actions.add(new ReapplyAction(this, diskVersion));
} else {
// nothing - system is up to date.
log.info("Disk and Database versions are in sync, checkums match");
}
}
else if (diskVersionNum.compareTo(dbVersionNum) > 0) {
// disk version larger than db version. apply deltas
for (int i=0; i 0) {
actions.add(new ApplyAction(this, dv));
}
}
} else { //diskVersionNumb.compareTo(dbVersionNum) < 0
// db version larger than disk version.
// must recommend manual rollback
}
return actions;
}
public void executeActionWithLock(Action a) throws SQLException, InterruptedException {
List actions=new ArrayList(1);
actions.add(a);
executeActionsWithLock(actions);
}
public void executeActionsWithLock(List actions) throws SQLException, InterruptedException {
log.info("Executing "+actions.size()+" actions.");
StringBuilder actionpath=new StringBuilder();
Action first=actions.get(0);
if (first instanceof InitAction) {
log.info("First Action is InitDB, executing before lock");
first.execute();
actionpath.append(first.toString());
actions.remove(0);
}
DBMLock lock=null;
try {
lock=dbmdata.acquireLock(DBM, LOCK_TIMEOUT);
log.debug("Obtained "+lock);
for (Action action:actions) {
boolean success=action.execute();
if (actionpath.length() > 0)
actionpath.append("->");
actionpath.append(action.toString()+": "+(success?"Success":"FAIL"));
}
} finally {
if (lock != null) {
log.debug("Releasing "+lock);
dbmdata.releaseLock(lock);
}
}
log.info(actionpath.toString());
reloadDBVersions();
}
public void updateAll() throws SQLException, IOException, InterruptedException {
List actions=recommendUpdateActions();
executeActionsWithLock(actions);
}
public Version getCurrentDBVersion() throws SQLException {
reloadDBVersions();
Version currentVersion=null;
log.info("Getting Current DB Version");
try {
DBVersion current=dbmdata.getCurrentVersion();
if (current != null)
currentVersion=current.getVersion();
else
currentVersion=new Version("-1");
} catch (Exception ex) {
log.info("Error getting current Version of database.");
}
return currentVersion;
}
public DBMLock getExistingLock() throws SQLException {
// Likely null.
return dbmdata.selectLock(DBM);
}
/** */
public boolean executeStatement(SQLStatement sql) throws SQLException {
log.info("Executing "+sql.getSql());
boolean success=false;
try {
int rows=-1;
String err=null;
Statement state=null;
try {
Connection con=conInfo.getDefaultConnection();
state=con.createStatement();
rows=state.executeUpdate(sql.getSql());
success=true;
} catch (Exception ex) {
log.error("Error executing\n "+sql.getSql(), ex);
String stack=DebugUtils.stackString(ex);
if (stack.length() > 4000) {
stack=stack.substring(0, 4000);
}
err=stack;
} finally {
DBUtils.close(state);
}
dbmdata.logStatement(sql, success, rows, err);
} finally {
}
return success;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy