/*
* Copyright 2010 NapkinDrawing LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.napkindrawing.dbversion.task;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.taskdefs.SQLExec;
import com.napkindrawing.dbversion.InstalledRevision;
import com.napkindrawing.dbversion.Profile;
import com.napkindrawing.dbversion.Revision;
import com.napkindrawing.dbversion.Version;
import com.napkindrawing.dbversion.loader.LoaderSpec;
import com.napkindrawing.dbversion.loader.ProfileLoader;
import com.napkindrawing.dbversion.loader.ProfilesLoader;
import com.napkindrawing.dbversion.loader.RevisionLoader;
import com.napkindrawing.dbversion.loader.RevisionsLoader;
import com.napkindrawing.dbversion.loader.fs.FileSystemLoader;
public abstract class DbVersionCommand extends SQLExec {
private List<Profile> profiles = new ArrayList<Profile>();
private Properties config = new Properties();
private Class<? extends LoaderSpec> loaderSpecClass = FileSystemLoader.class;
private ProfilesLoader profilesLoader;
private ProfileLoader profileLoader;
private RevisionsLoader revisionsLoader;
private RevisionLoader revisionLoader;
protected Boolean checkInitTables = true;
protected List<InstalledRevision> installedRevisions = null;
public DbVersionCommand() {
super();
}
private Map<Integer,String> _urlHostSchema = new HashMap<Integer,String>();
public String getHostSchema() {
if(getUrl() == null || getUrl().isEmpty()) {
return null;
}
if(_urlHostSchema.containsKey(getUrl().hashCode())) {
return _urlHostSchema.get(getUrl().hashCode());
}
Pattern p = Pattern.compile("^jdbc:\\w+://([\\w\\.\\-\\_\\:]+/[\\w\\.\\-\\_]+).*$");
Matcher m = p.matcher(getUrl());
if(!m.matches()) {
throw new BuildException("Couldn't extract hostname and schema from jdbc url: " + getUrl());
}
String hostSchema = m.group(1);
_urlHostSchema.put(getUrl().hashCode(), hostSchema);
return hostSchema;
}
protected String prependLogPrefix(String msg) {
String hostSchema = getHostSchema();
if(hostSchema != null && !hostSchema.isEmpty()) {
String prefix = "[" + hostSchema + "] ";
if(msg.startsWith(prefix)) {
return msg;
}
return prefix + msg;
}
return msg;
}
@Override
public void log(String msg, int msgLevel) {
super.log(prependLogPrefix(msg), msgLevel);
}
@Override
public void log(String msg, Throwable t, int msgLevel) {
super.log(prependLogPrefix(msg), t, msgLevel);
}
@Override
public void log(String msg) {
super.log(prependLogPrefix(msg));
}
@Override
public void log(Throwable t, int msgLevel) {
super.log(t, msgLevel);
}
@Override
public void init() {
super.init();
LoaderSpec loaderSpec;
try {
loaderSpec = loaderSpecClass.newInstance();
profilesLoader = loaderSpec.getProfilesLoaderClass().newInstance();
profileLoader = loaderSpec.getProfileLoaderClass().newInstance();
revisionsLoader = loaderSpec.getRevisionsLoaderClass().newInstance();
revisionLoader = loaderSpec.getRevisionLoaderClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error initializing loaders from spec " + loaderSpecClass.getName(),e);
}
profilesLoader.setProfileLoader(profileLoader);
profileLoader.setRevisionsLoader(revisionsLoader);
revisionsLoader.setRevisionLoader(revisionLoader);
}
public void execute() {
profilesLoader.configure(config);
profileLoader.configure(config);
revisionsLoader.configure(config);
revisionLoader.configure(config);
setProfiles( profilesLoader.loadProfiles() );
summarizeProfiles();
if( checkInitTables && !canQueryRevisionTable()) {
throw new BuildException("Database at " + getUrl() + " not initialized");
}
if(checkInitTables) {
loadInstalledRevisions();
}
}
private Map<String, Version> maxVersionByProfile = new HashMap<String, Version>();
public Version getMaxVersion(String profileName) {
Version v = maxVersionByProfile.get(profileName);
return v == null ? Version.NONE : v;
}
private Map<String,Profile> profilesByName = new HashMap<String,Profile>();
public Profile getProfileByName(String profileName) {
if(!profilesByName.containsKey(profileName)) {
throw new BuildException("No profile named '"+profileName+"'");
}
return profilesByName.get(profileName);
}
protected void summarizeProfiles() {
for(Profile profile : getProfiles()) {
profilesByName.put(profile.getName(), profile);
for(Revision revision : profile.getRevisions()) {
String pn = profile.getName();
Version v = revision.getVersion();
if(!maxVersionByProfile.containsKey(pn)) {
maxVersionByProfile.put(pn, v);
profile.setMaxVersion(v);
} else {
if(maxVersionByProfile.get(pn).compareTo(v) < 0) {
maxVersionByProfile.put(pn, v);
profile.setMaxVersion(v);
}
}
}
if(profile.getMaxVersion() == null) {
profile.setMaxVersion(Version.NONE);
}
}
}
private Map<String, Version> maxInstalledVersionByProfile = new HashMap<String, Version>();
public Version getMaxInstalledVersion(String profileName) {
Version v = maxInstalledVersionByProfile.get(profileName);
return v == null ? Version.NONE : v;
}
protected void summarizeInstalledRevisions() {
for(InstalledRevision i : installedRevisions) {
String pn = i.getProfileName();
Version v = i.getVersion();
getProfileByName(pn).getInstalledRevisions().add(i);
if(!installedProfileNames.contains(pn)) {
installedProfileNames.add(pn);
}
if(lastInstalledRevision == null ||
lastInstalledRevision.getUpgradeDate().compareTo(i.getUpgradeDate()) < 0) {
lastInstalledRevision = i;
}
if(!maxInstalledVersionByProfile.containsKey(pn)) {
maxInstalledVersionByProfile.put(pn, v);
getProfileByName(pn).setMaxInstalledVersion(v);
} else {
if(maxInstalledVersionByProfile.get(pn).compareTo(v) < 0) {
maxInstalledVersionByProfile.put(pn, v);
getProfileByName(pn).setMaxInstalledVersion(v);
}
}
}
for(Profile p : getProfiles()) {
if(p.getMaxInstalledVersion() == null) {
p.setMaxInstalledVersion(Version.NONE);
}
}
}
protected InstalledRevision lastInstalledRevision = null;
public InstalledRevision getLastInstalledRevision() {
if(installedRevisions == null) {
loadInstalledRevisions();
}
return lastInstalledRevision;
}
protected void loadInstalledRevisions() {
installedRevisions = new ArrayList<InstalledRevision>();
Connection conn = getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM __database_revision");
installedRevisions = new ArrayList<InstalledRevision>();
while(rs.next()) {
installedRevisions.add(new InstalledRevision(rs));
}
log(installedRevisions.size() + " Revisions Read", Project.MSG_DEBUG);
} catch(SQLException e) {
throw new RuntimeException("Error querying database", e);
} finally {
if (stmt != null) {
try {stmt.close();}catch (SQLException ignore) {}
}
}
summarizeInstalledRevisions();
}
protected List<String> installedProfileNames = new ArrayList<String>();
public List<String> getInstalledProfileNames() {
if(installedProfileNames == null) {
loadInstalledRevisions();
}
return installedProfileNames;
}
public List<InstalledRevision> getInstalledRevisions() {
if(installedRevisions == null) {
loadInstalledRevisions();
}
return installedRevisions;
}
public boolean canQueryRevisionTable() {
Connection conn = getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT count(*), '"+Thread.currentThread() + "' AS thread FROM __database_revision");
if(rs.first()) {
return true;
}
} catch(SQLException e) {
if(e.getMessage().matches("^Table.*doesn't exist.*$")) {
// MySQL
} else {
throw new RuntimeException("Error querying database", e);
}
} finally {
if (stmt != null) {
try {stmt.close();}catch (SQLException ignore) {}
}
}
return false;
}
public List<Profile> getProfiles() {
return profiles;
}
public void setProfiles(List<Profile> profiles) {
this.profiles = profiles;
}
public void addConfiguredLoaderProperty(Property property) {
log("Adding loaderProperty: " + property.getName() +" => " + property.getValue(), Project.MSG_DEBUG);
config.put(property.getName(), property.getValue());
}
public void setLoaderSpecClass(Class<? extends LoaderSpec> loaderSpecClass) {
this.loaderSpecClass = loaderSpecClass;
}
protected String loadResourceFile(String path) {
log("Attempting to load resource file: " + path);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
log("Loader: " + loader);
InputStream createInputStream = loader.getResourceAsStream(path);
if(createInputStream == null) {
throw new RuntimeException("Couldnt' load resource file: " + path);
}
String contents;
try {
contents = IOUtils.toString(createInputStream);
} catch (IOException e1) {
throw new RuntimeException(e1);
}
return contents;
}
protected void closeQuietly() {
try {
if (getStatement() != null) {
getStatement().close();
}
} catch (SQLException ex) {
// ignore
}
try {
if (getConnection() != null) {
getConnection().close();
}
} catch (SQLException ex) {
// ignore
}
}
public Boolean getCheckInitTables() {
return checkInitTables;
}
public void setCheckInitTables(Boolean checkInitTables) {
this.checkInitTables = checkInitTables;
}
}