KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sslexplorer > jdbc > DBUpgrader


1 /*
2  * SSL-Explorer
3  *
4  * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */

19             
20 package com.sslexplorer.jdbc;
21
22 import java.io.BufferedReader JavaDoc;
23 import java.io.File JavaDoc;
24 import java.io.FileInputStream JavaDoc;
25 import java.io.FileOutputStream JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.io.InputStreamReader JavaDoc;
29 import java.io.OutputStream JavaDoc;
30 import java.io.PrintWriter JavaDoc;
31 import java.sql.SQLException JavaDoc;
32 import java.util.ArrayList JavaDoc;
33 import java.util.Collections JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Properties JavaDoc;
38 import java.util.prefs.Preferences JavaDoc;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 import com.sslexplorer.boot.ContextHolder;
44 import com.sslexplorer.boot.Util;
45 import com.sslexplorer.boot.VersionInfo;
46
47 /**
48  * Checks database schemas to see if they are up to date, running the database
49  * upgrade scripts if they are not.
50  * <p>
51  * Current database versions are stored using the Java Preferences API.
52  * <p>
53  * TODO This class currently assumes that HSQLDB is in use (to check for the
54  * existance of databases), although it should be relatively to adapt it for use
55  * with other database implementations.
56  *
57  * @author Brett Smith <a HREF="mailto: brett@3sp.com">&lt;brett@3sp.com&gt;</a>
58  */

59 public class DBUpgrader {
60
61     final static Log log = LogFactory.getLog(DBUpgrader.class);
62
63     //
64
private static HashMap JavaDoc removed = new HashMap JavaDoc();
65     private static HashMap JavaDoc removeProcessed = new HashMap JavaDoc();
66
67     // Private instance variables
68

69     private JDBCDatabaseEngine engine;
70     private File JavaDoc dbDir;
71     private VersionInfo.Version newDbVersion;
72     private File JavaDoc upgradeDir;
73     private File JavaDoc versionsFile;
74     private boolean useDbNameForVersionCheck;
75
76     /**
77      * Constructor
78      *
79      * @param newDbVersion the new version of the database
80      * @param engine the database engine to use to execute the commands
81      * @param dbDir directory containing databases
82      * @param upgradeDir directory containing upgrade script
83      */

84     public DBUpgrader(VersionInfo.Version newDbVersion, JDBCDatabaseEngine engine, File JavaDoc dbDir, File JavaDoc upgradeDir) {
85         this(null, newDbVersion, engine, dbDir, upgradeDir);
86     }
87
88     /**
89      * Constructor
90      *
91      * @param versionsFile file to store database version
92      * @param newDbVersion the new version of the database
93      * @param engine the database engine to use to execute the commands
94      * @param dbDir directory containing databases
95      * @param upgradeDir directory containing upgrade script
96      */

97     public DBUpgrader(File JavaDoc versionsFile, VersionInfo.Version newDbVersion, JDBCDatabaseEngine engine, File JavaDoc dbDir, File JavaDoc upgradeDir) {
98         this(versionsFile, newDbVersion, engine, dbDir, upgradeDir, false);
99     }
100
101     /**
102      * Constructor
103      *
104      * @param versionsFile file to store database version
105      * @param newDbVersion the new version of the database
106      * @param engine the database engine to use to execute the commands
107      * @param dbDir directory containing databases
108      * @param upgradeDir directory containing upgrade script
109      * @param useDbNameForVersionCheck use database name for version check
110      * instead of alias
111      */

112     public DBUpgrader(File JavaDoc versionsFile, VersionInfo.Version newDbVersion, JDBCDatabaseEngine engine, File JavaDoc dbDir, File JavaDoc upgradeDir,
113                       boolean useDbNameForVersionCheck) {
114         super();
115         this.versionsFile = versionsFile;
116         this.upgradeDir = upgradeDir;
117         this.engine = engine;
118         this.dbDir = dbDir;
119         this.newDbVersion = newDbVersion;
120         this.useDbNameForVersionCheck = useDbNameForVersionCheck;
121     }
122
123     /**
124      * Check the database schema and perform any upgrades.
125      *
126      * @throws Exception on any error
127      */

128     public void upgrade() throws Exception JavaDoc {
129         Properties JavaDoc versions = null;
130         if (versionsFile == null) {
131             /* If required, convert from the old preferences node to the new
132              * file (version 0.2.5)
133              */

134             versionsFile = new File JavaDoc(ContextHolder.getContext().getDBDirectory(), "versions.log");
135             Preferences JavaDoc p = ContextHolder.getContext().getPreferences().node("dbupgrader");
136             if (p.nodeExists("currentDataVersion")) {
137                 log.warn("Migrating database versions from preferences to properties file in "
138                                 + ContextHolder.getContext().getDBDirectory().getAbsolutePath() + ".");
139                 versions = new Properties JavaDoc();
140                 p = p.node("currentDataVersion");
141                 String JavaDoc[] c = p.keys();
142                 for (int i = 0; i < c.length; i++) {
143                     versions.put(c[i], p.get(c[i], ""));
144                 }
145                 FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(versionsFile);
146                 try {
147                     versions.store(fos, "SSL-Explorer Database versions");
148                 } finally {
149                     Util.closeStream(fos);
150                 }
151                 p.removeNode();
152             }
153         }
154
155         // Load the database versions
156
if (versions == null) {
157             versions = new Properties JavaDoc();
158             if (versionsFile.exists()) {
159                 FileInputStream JavaDoc fin = new FileInputStream JavaDoc(versionsFile);
160                 try {
161                     versions.load(fin);
162                 } finally {
163                     Util.closeStream(fin);
164                 }
165             }
166         }
167
168         try {
169             String JavaDoc dbCheckName = useDbNameForVersionCheck ? engine.getDatabase() : engine.getAlias();
170
171             if ((!engine.isDatabaseExists() || removed.containsKey(engine.getDatabase()))
172                             && !removeProcessed.containsKey(dbCheckName)) {
173                 versions.remove(dbCheckName);
174                 removeProcessed.put(dbCheckName, Boolean.TRUE);
175                 if (log.isInfoEnabled())
176                     log.info("Database for " + dbCheckName + " (" + engine.getDatabase()
177                                     + ") has been removed, assuming this is a re-install.");
178                 removed.put(engine.getDatabase(), Boolean.TRUE);
179             }
180
181             // Check for any SQL scripts to run to bring the databases up to
182
// date
183
VersionInfo.Version currentDataVersion = new VersionInfo.Version(versions.getProperty(dbCheckName, "0.0.0"));
184             if (log.isInfoEnabled()) {
185                 log.info("New logical database version for " + engine.getAlias() + " is " + newDbVersion);
186                 log.info("Current logical database version for " + engine.getAlias() + " is " + currentDataVersion);
187                 //
188
log.info("Upgrade script directory is " + upgradeDir.getAbsolutePath());
189             }
190             List JavaDoc upgrades = getSortedUpgrades(upgradeDir);
191             File JavaDoc oldLog = new File JavaDoc(upgradeDir, "upgrade.log");
192             if (!dbDir.exists()) {
193                 if (!dbDir.mkdirs()) {
194                     throw new Exception JavaDoc("Failed to create database directory " + dbDir.getAbsolutePath());
195                 }
196             }
197             File JavaDoc logFile = new File JavaDoc(dbDir, "upgrade.log");
198             if (oldLog.exists()) {
199                 if (log.isInfoEnabled())
200                     log.info("Moving upgrade.log to new location (as of version 0.1.5 it resides in the db directory.");
201                 if (!oldLog.renameTo(logFile)) {
202                     throw new Exception JavaDoc("Failed to move upgrade log file from " + oldLog.getAbsolutePath() + " to "
203                                     + logFile.getAbsolutePath());
204                 }
205             }
206             HashMap JavaDoc completedUpgrades = new HashMap JavaDoc();
207             if (!logFile.exists()) {
208                 OutputStream JavaDoc out = null;
209                 try {
210                     out = new FileOutputStream JavaDoc(logFile);
211                     PrintWriter JavaDoc writer = new PrintWriter JavaDoc(out, true);
212                     writer.println("# This file contains a list of database upgrades");
213                     writer.println("# that have completed correctly.");
214                 } finally {
215                     if (out != null) {
216                         out.flush();
217                         out.close();
218                     }
219                 }
220             } else {
221                 InputStream JavaDoc in = null;
222                 try {
223                     in = new FileInputStream JavaDoc(logFile);
224                     BufferedReader JavaDoc reader = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(in));
225                     String JavaDoc line = null;
226                     while ((line = reader.readLine()) != null) {
227                         line = line.trim();
228                         if (!line.equals("") && !line.startsWith("#")) {
229                             completedUpgrades.put(line, line);
230                         }
231                     }
232                 } finally {
233                     if (in != null) {
234                         in.close();
235                     }
236                 }
237             }
238             OutputStream JavaDoc out = null;
239             try {
240                 out = new FileOutputStream JavaDoc(logFile, true);
241                 PrintWriter JavaDoc writer = new PrintWriter JavaDoc(out, true);
242                 Class.forName("org.hsqldb.jdbcDriver"); // shouldnt be needed,
243
// but
244
// just in
245
// case
246
for (Iterator JavaDoc i = upgrades.iterator(); i.hasNext();) {
247                     DBUpgradeOp upgrade = (DBUpgradeOp) i.next();
248                     boolean runBefore = completedUpgrades.containsKey(upgrade.getFile().getName());
249                     if (log.isInfoEnabled())
250                         log.info("Checking if upgrade " + upgrade.getFile() + " [" + upgrade.getVersion()
251                                         + "] needs to be run. Run before = " + runBefore + ". Current data version = "
252                                         + currentDataVersion + ", upgrade version = " + upgrade.getVersion());
253                     if ((!runBefore || (currentDataVersion.getMajor() == 0 && currentDataVersion.getMinor() == 0 && currentDataVersion
254                                     .getBuild() == 0))
255                                     && upgrade.getVersion().compareTo(currentDataVersion) >= 0
256                                     && upgrade.getVersion().compareTo(newDbVersion) < 0) {
257                         if (log.isInfoEnabled())
258                             log.info("Running script " + upgrade.getName() + " [" + upgrade.getVersion() + "] on database "
259                                             + engine.getDatabase());
260
261                         // Get a JDBC connection
262
JDBCConnectionImpl conx = engine.aquireConnection();
263                         try {
264                             runSQLScript(conx, upgrade.getFile());
265                             completedUpgrades.put(upgrade.getFile().getName(), upgrade.getFile().getName());
266                             writer.println(upgrade.getFile().getName());
267                         } finally {
268                             engine.releaseConnection(conx);
269                         }
270                     }
271                 }
272                 versions.put(dbCheckName, newDbVersion.toString());
273
274                 if (log.isInfoEnabled())
275                     log.info("Logical database " + engine.getAlias() + " (" + engine.getDatabase() + ") is now at version "
276                                     + newDbVersion);
277             } finally {
278                 if (out != null) {
279                     out.flush();
280                     out.close();
281                 }
282             }
283         } finally {
284             FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(versionsFile);
285             try {
286                 versions.store(fos, "SSL-Explorer Database versions");
287             } finally {
288                 Util.closeStream(fos);
289             }
290         }
291     }
292
293     private List JavaDoc getSortedUpgrades(File JavaDoc upgradeDir) {
294         List JavaDoc sortedUpgrades = new ArrayList JavaDoc();
295         File JavaDoc[] files = upgradeDir.listFiles();
296         for (int i = 0; files != null && i < files.length; i++) {
297             String JavaDoc n = files[i].getName();
298             if (n.endsWith(engine.getAlias() + ".sql")) {
299                 sortedUpgrades.add(new DBUpgradeOp(files[i]));
300             } else {
301                 if (log.isDebugEnabled())
302                     log.debug("Skipping script " + n);
303             }
304
305         }
306         Collections.sort(sortedUpgrades);
307         return sortedUpgrades;
308     }
309
310     private void runSQLScript(JDBCConnectionImpl con, File JavaDoc sqlFile) throws SQLException JavaDoc, IllegalStateException JavaDoc, IOException JavaDoc {
311
312         InputStream JavaDoc in = null;
313         try {
314             in = new FileInputStream JavaDoc(sqlFile);
315             BufferedReader JavaDoc reader = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(in));
316             String JavaDoc line = null;
317             StringBuffer JavaDoc cmdBuffer = new StringBuffer JavaDoc();
318             boolean quoted = false;
319             char ch;
320             while ((line = reader.readLine()) != null) {
321                 line = line.trim();
322                 if (!line.equals("") && !line.startsWith("//") && !line.startsWith("--") && !line.startsWith("#")) {
323                     quoted = false;
324                     for (int i = 0; i < line.length(); i++) {
325                         ch = line.charAt(i);
326                         if (ch == '\'') {
327                             if (quoted) {
328                                 if ((i + 1) < line.length() && line.charAt(i + 1) == '\'') {
329                                     i++;
330                                     cmdBuffer.append(ch);
331                                 } else {
332                                     quoted = false;
333                                 }
334                             } else {
335                                 quoted = true;
336                             }
337                             cmdBuffer.append(ch);
338                         } else if (ch == ';' && !quoted) {
339                             if (cmdBuffer.length() > 0) {
340                                 executeSQLStatement(con, cmdBuffer.toString());
341                                 cmdBuffer.setLength(0);
342                             }
343                         } else {
344                             if (i == 0 && ch != ' ' && cmdBuffer.length() > 0 && !quoted) {
345                                 cmdBuffer.append(' ');
346                             }
347                             cmdBuffer.append(ch);
348                         }
349                     }
350                 }
351             }
352             if (cmdBuffer.length() > 0) {
353                 executeSQLStatement(con, cmdBuffer.toString());
354                 cmdBuffer.setLength(0);
355             }
356         } finally {
357             if (in != null) {
358                 in.close();
359             }
360         }
361     }
362
363     private void executeSQLStatement(JDBCConnectionImpl con, String JavaDoc cmd) throws SQLException JavaDoc {
364         /*
365          * A hack to get around the problem of moving the HSQLDB stored
366          * procedures. Prior to version 0.1.13, these functions existed in the
367          * class com.sslexplorer.DBFunctions. At version 0.1.13, this were moved
368          * to com.sslexplorer.server.hsqldb.DBFunctions. This meant that on a
369          * fresh install, when the original 'CREATE ALIAS' statement is
370          * encountered it can no longer find the class. The 'CREATE ALIAS' in
371          * the 0.1.13 upgrade scripts have the correct classname so upgrades are
372          * not affected by this.
373          *
374          * This then happend *AGAIN* for 0.2.0.
375          *
376          * TODO remove this code when we clear out all the database upgrade
377          * scripts and start again
378          *
379          */

380         if (cmd.startsWith("CREATE ALIAS ")) {
381             int idx = cmd.indexOf("com.sslexplorer.DBFunctions.");
382             if (idx != -1) {
383                 cmd = cmd.substring(0, idx) + "com.sslexplorer.server.hsqldb.DBFunctions." + cmd.substring(idx + 28);
384             }
385             idx = cmd.indexOf("com.sslexplorer.server.hsqldb.DBFunctions.");
386             if (idx != -1) {
387                 cmd = cmd.substring(0, idx) + "com.sslexplorer.jdbc.hsqldb.DBFunctions." + cmd.substring(idx + 42);
388             }
389
390         }
391
392         if (log.isDebugEnabled())
393             log.debug("Executing \"" + cmd + "\"");
394         con.execute(cmd);
395     }
396
397 }
398
Popular Tags