Java tutorial
/* * 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.File; import java.io.StringWriter; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import name.fraser.neil.plaintext.diff_match_patch; import name.fraser.neil.plaintext.diff_match_patch.Diff; import net.sf.json.JSONObject; import net.sf.json.JSONSerializer; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.input.CharSequenceReader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import com.napkindrawing.dbversion.InstalledRevision; import com.napkindrawing.dbversion.Profile; import com.napkindrawing.dbversion.Revision; import com.napkindrawing.dbversion.Version; import freemarker.template.Configuration; import freemarker.template.Template; public class DbVersionUpgrade extends DbVersionProfileCommand { public String templateData; public String templateDataFiles; protected JSONObject parsedTemplateData = new JSONObject(); public DbVersionUpgrade() { super(); } @Override public void execute() throws BuildException { super.execute(); parseTemplateData(); System.out.printf("Upgrading database to latest version: %s\n", getUrl()); try { verifySchemaMatch(); for (String profileName : getProfileNamesArray()) { log("Upgrading profile: " + profileName); execute(getProfileByName(profileName)); } } finally { closeQuietly(); } } public void execute(Profile profile) { System.out.printf("Checking profile for upgrade: %s\n", profile.getName()); Version maxInstalledVersion = getMaxInstalledVersion(profile.getName()); Version maxVersion = getMaxVersion(profile.getName()); if (maxVersion == Version.NONE) { log("No revisions available for install"); } else if (maxInstalledVersion.compareTo(maxVersion) < 0) { log("Profile needs upgrade"); log("Installed: " + maxInstalledVersion, Project.MSG_VERBOSE); log("Available: " + maxVersion, Project.MSG_VERBOSE); verifyPriorInstalledRevisions(profile, maxInstalledVersion); performUpgrade(profile, maxInstalledVersion); } else if (maxInstalledVersion.compareTo(maxVersion) == 0) { log("Profile is up-to-date (Version " + maxVersion + ")"); verifyPriorInstalledRevisions(profile, maxVersion); } else if (maxInstalledVersion.compareTo(maxVersion) > 0) { throw new BuildException("Installed version is greater than max local version", getLocation()); } } protected Map<Profile, Map<Version, String>> compiledTemplates = new HashMap<Profile, Map<Version, String>>(); protected Configuration _fmConfig = new Configuration(); protected String getCompiledTemplate(Profile profile, Revision revision) { if (compiledTemplates.containsKey(profile) && compiledTemplates.get(profile).containsKey(revision.getVersion())) { return compiledTemplates.get(profile).get(revision.getVersion()); } CharSequenceReader templateReader = new CharSequenceReader(revision.getUpgradeScriptTemplate()); String compiledTemplate = null; try { Template fmTemplate = new Template(revision.getName(), templateReader, _fmConfig); StringWriter templateWriter = new StringWriter(); fmTemplate.process(parsedTemplateData, templateWriter); compiledTemplate = templateWriter.toString(); } catch (Exception e) { throw new BuildException(e, getLocation()); } if (!compiledTemplates.containsKey(profile)) { compiledTemplates.put(profile, new HashMap<Version, String>()); } compiledTemplates.get(profile).put(revision.getVersion(), compiledTemplate); return compiledTemplate; } protected void parseTemplateData() { if (getConnection() != null) { try { parsedTemplateData.put("dbSchema", getConnection().getCatalog()); } catch (SQLException e) { throw new BuildException("Couldn't extract catalog from connection", getLocation()); } } if (templateDataFiles != null && !templateDataFiles.isEmpty()) { try { String[] files = templateDataFiles.split("\\s*,\\s*"); log("Totally parsing " + files.length + " files!!!", Project.MSG_INFO); for (String file : files) { String fileContents = FileUtils.readFileToString(new File(file)); parsedTemplateData.putAll((JSONObject) JSONSerializer.toJSON(fileContents)); } } catch (Exception e) { throw new BuildException(e, getLocation()); } } if (templateData != null && !templateData.isEmpty()) { parsedTemplateData.putAll((JSONObject) JSONSerializer.toJSON(templateData)); } } public void setTemplateData(String templateData) { this.templateData = templateData; } public void setTemplateDataFiles(String templateDataFiles) { this.templateDataFiles = templateDataFiles; } public String getParsedTemplateData() { return parsedTemplateData.toString(); } /** * * @param profile * @param from - Exclusive version */ public void performUpgrade(Profile profile, Version from) { log("Upgrading profile " + profile.getName()); for (Revision revision : profile.getRevisions()) { if (from.compareTo(revision.getVersion()) < 0) { applyRevision(profile, revision); } } log("Upgrade complete for profile " + profile.getName()); } private void verifyPriorInstalledRevisions(Profile profile, Version upTo) { if (upTo == Version.NONE) { return; } log("Verifying prior installed revisions up to " + upTo); for (Revision rev : profile.getRevisions()) { if (rev.getVersion().compareTo(upTo) <= 0) { log("Verifying installed revision " + rev.getVersion()); InstalledRevision installedRev = profile.getInstalledRevision(rev.getVersion()); if (!rev.getUpgradeScriptTemplateChecksum() .equals(installedRev.getUpgradeScriptTemplateChecksum())) { logDiffs("Installed Template", "Local Template", installedRev.getUpgradeScriptTemplate(), rev.getUpgradeScriptTemplate()); throw new BuildException( "Prior installed revision " + rev.getVersion() + " has different template checksum!", getLocation()); } String compiledTmpl = getCompiledTemplate(profile, rev); String compiledTmplCksum = DigestUtils.md5Hex(compiledTmpl); if (!installedRev.getUpgradeScriptCompiledChecksum().equals(compiledTmplCksum)) { logDiffs("Installed Compiled Template", "Local Compiled Template", installedRev.getUpgradeScriptCompiled(), compiledTmpl); throw new BuildException("Prior installed revision " + rev.getVersion() + " compiled template differs from local!", getLocation()); } } } } private void verifySchemaMatch() { log("Verifying schema", Project.MSG_DEBUG); InstalledRevision installedRev = getLastInstalledRevision(); if (installedRev == null) { log("No installed revisions, not verifying schema", Project.MSG_DEBUG); return; } StringBuilder schemaDump = dumpSchema(); String schemaDumpStr = schemaDump.toString(); String schemaDumpCksum = DigestUtils.md5Hex(schemaDumpStr); String priorCksum = installedRev.getPostUpgradeSchemaDumpChecksum(); if (!priorCksum.equals(schemaDumpCksum)) { logDiffs("Prior Schema", "Current Schema", installedRev.getPostUpgradeSchemaDump(), schemaDumpStr); throw new BuildException("Prior upgrade schema dump doesn't match existing schema!", getLocation()); } } protected void logDiffs(String title1, String title2, String text1, String text2) { diff_match_patch dmp = new diff_match_patch(); LinkedList<Diff> diffs = dmp.diff_main(text1, text2); dmp.diff_cleanupSemantic(diffs); log("================================================================================"); log("" + title1 + ":"); log("--------------------------------------------------------------------------------"); log(text1); log("================================================================================"); log("================================================================================"); log("" + title2 + ":"); log("--------------------------------------------------------------------------------"); log(text2); log("================================================================================"); log("================================================================================"); log("Diffs:"); for (Diff diff : diffs) { String headerText = ""; switch (diff.operation) { case INSERT: headerText = "Unexpected Text"; break; case DELETE: headerText = "Missing Text"; break; case EQUAL: continue; } log("================================================================================"); log(headerText); log("--------------------------------------------------------------------------------"); log(diff.text); log("================================================================================"); } } public void applyRevision(Profile profile, Revision revision) { log("Applying revision " + revision.getVersion()); log("Upgrade Script Template:\n\n" + revision.getUpgradeScriptTemplate() + "\n\n", Project.MSG_DEBUG); String compiledTemplate = getCompiledTemplate(profile, revision); log("Upgrade Script Compiled:\n\n" + compiledTemplate + "\n\n", Project.MSG_DEBUG); try { if (getConnection() == null) { throw new BuildException("Couldn't connect to database", getLocation()); } runStatements(new CharSequenceReader(compiledTemplate), System.out); if (!isAutocommit()) { log("Committing transaction", Project.MSG_DEBUG); getConnection().commit(); } } catch (Exception e) { throw new BuildException(e, getLocation()); } InstalledRevision installedRevision = new InstalledRevision(profile, revision); installedRevision.setUpgradeScriptCompiled(compiledTemplate); installedRevision.assignUpgradeScriptCompiledChecksum(); installedRevision.setUpgradeScriptData(getParsedTemplateData()); logRevision(installedRevision); } protected void logRevision(InstalledRevision installedRevision) { log("Logging upgrade revision", Project.MSG_VERBOSE); PreparedStatement logStmt = null; try { StringBuilder schemaDump = dumpSchema(); String schemaDumpStr = schemaDump.toString(); String logSql = loadResourceFile("com/napkindrawing/dbversion/logRevision.sql"); logStmt = getConnection().prepareStatement(logSql); logStmt.setString(1, installedRevision.getProfileName()); logStmt.setString(2, installedRevision.getVersion().getId()); logStmt.setString(3, installedRevision.getName()); logStmt.setString(4, installedRevision.getUpgradeScriptTemplate()); logStmt.setString(5, installedRevision.getUpgradeScriptTemplateChecksum()); logStmt.setString(6, installedRevision.getUpgradeScriptData()); logStmt.setString(7, installedRevision.getUpgradeScriptCompiled()); logStmt.setString(8, installedRevision.getUpgradeScriptCompiledChecksum()); logStmt.setString(9, schemaDumpStr); logStmt.setString(10, DigestUtils.md5Hex(schemaDumpStr)); logStmt.setLong(11, System.currentTimeMillis()); logStmt.execute(); if (!isAutocommit()) { log("Committing transaction", Project.MSG_VERBOSE); getConnection().commit(); } } catch (Exception e) { throw new BuildException(e, getLocation()); } finally { try { if (logStmt != null) logStmt.close(); } catch (SQLException e) { } } } protected StringBuilder dumpSchema() { StringBuilder dump = new StringBuilder(); SortedSet<String> tableNames = new TreeSet<String>(); Statement stmt = null; try { stmt = getConnection().createStatement(); ResultSet tablesRs = stmt.executeQuery("SHOW TABLES"); while (tablesRs.next()) { tableNames.add(tablesRs.getString(1)); } for (String tableName : tableNames) { ResultSet describeRs = stmt.executeQuery("SHOW CREATE TABLE " + tableName); if (!describeRs.next()) { throw new BuildException("Couldn't retrieve create sql for table " + tableName); } String tableCreateSql = describeRs.getString(2); tableCreateSql = tableCreateSql.replaceFirst(" AUTO_INCREMENT=\\d+ ", " "); dump.append(tableCreateSql); dump.append("\n;\n"); } } catch (Exception e) { throw new BuildException(e, getLocation()); } finally { if (stmt != null) { try { stmt.close(); } catch (Exception e) { } } } return dump; } }