Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that implements database configuration methods
4
 *  Copyright: 2008 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Michael Daigle
7
 * 
8
 *   '$Author: daigle $'
9
 *     '$Date: 2008-07-07 13:49:18 -0700 (Mon, 07 Jul 2008) $'
10
 * '$Revision: 4084 $'
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
 */
26

    
27
package edu.ucsb.nceas.metacat.admin;
28

    
29
import java.sql.Connection;
30
import java.sql.PreparedStatement;
31
import java.sql.SQLException;
32
import java.sql.ResultSet;
33
import java.sql.Statement;
34
import java.sql.Timestamp;
35

    
36
import java.io.BufferedReader;
37
import java.io.FileInputStream;
38
import java.io.IOException;
39
import java.io.InputStreamReader;
40

    
41
import java.util.Date;
42
import java.util.HashSet;
43
import java.util.TreeSet;
44
import java.util.Vector;
45

    
46
import javax.servlet.ServletException;
47
import javax.servlet.http.HttpServletRequest;
48
import javax.servlet.http.HttpServletResponse;
49
import javax.servlet.http.HttpSession;
50

    
51
import edu.ucsb.nceas.metacat.DBConnection;
52
import edu.ucsb.nceas.metacat.DBConnectionPool;
53
import edu.ucsb.nceas.metacat.DBVersion;
54
import edu.ucsb.nceas.metacat.MetaCatVersion;
55
import edu.ucsb.nceas.metacat.service.PropertyService;
56
import edu.ucsb.nceas.metacat.util.DatabaseUtil;
57
import edu.ucsb.nceas.metacat.util.RequestUtil;
58
import edu.ucsb.nceas.metacat.util.SystemUtil;
59
import edu.ucsb.nceas.metacat.util.UtilException;
60

    
61
import edu.ucsb.nceas.utilities.DBUtil;
62
import edu.ucsb.nceas.utilities.FileUtil;
63
import edu.ucsb.nceas.utilities.GeneralPropertyException;
64
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
65

    
66
import org.apache.log4j.Logger;
67

    
68
/**
69
 * Control the display of the database configuration page and the processing
70
 * of the configuration values.
71
 */
72
public class DBAdmin extends MetaCatAdmin {
73
	// db statuses used by discovery code
74
	public static final int DB_DOES_NOT_EXIST = 0;
75
	public static final int TABLES_DO_NOT_EXIST = 1;
76
	public static final int TABLES_EXIST = 2;
77

    
78
	// db version statuses. This allows us to keep version history
79
	// in the db. Only the latest version record should be active.
80
	public static final int VERSION_INACTIVE = 0;
81
	public static final int VERSION_ACTIVE = 1;
82

    
83
	private TreeSet<DBVersion> versionSet = null;
84

    
85
	private static DBAdmin dbAdmin = null;
86
	private Logger logMetacat = Logger.getLogger(DBAdmin.class);
87
	private HashSet<String> sqlCommandSet = new HashSet<String>();
88
//	private MetaCatVersion metacatVersion = null;
89
	private static DBVersion databaseVersion = null;
90

    
91
	/**
92
	 * private constructor since this is a singleton
93
	 */
94
	private DBAdmin() throws AdminException {
95
		sqlCommandSet.add("INSERT");
96
		sqlCommandSet.add("UPDATE");
97
		sqlCommandSet.add("DELETE");
98
		sqlCommandSet.add("ALTER");
99
		sqlCommandSet.add("CREATE");
100
		sqlCommandSet.add("DROP");
101
		sqlCommandSet.add("BEGIN");
102
		sqlCommandSet.add("COMMIT");
103

    
104
		// gets all the upgrade version objects
105
		try {
106
			versionSet = DatabaseUtil.getUpgradeVersions();
107
		} catch (PropertyNotFoundException pnfe) {
108
			throw new AdminException("Could not retrieve database upgrade " 
109
					+ "versions during instantiation" + pnfe.getMessage());
110
		} catch (NumberFormatException nfe) {
111
			throw new AdminException("Bad version format numbering: "
112
					+ nfe.getMessage());
113
		}
114
	}
115

    
116
	/**
117
	 * Get the single instance of DBAdmin.
118
	 * 
119
	 * @return the single instance of DBAdmin
120
	 */
121
	public static DBAdmin getInstance() throws AdminException {
122
		if (dbAdmin == null) {
123
			dbAdmin = new DBAdmin();
124
		}
125
		return dbAdmin;
126
	}
127

    
128
	/**
129
	 * Handle configuration of the database the first time that Metacat starts
130
	 * or when it is explicitly called. Collect necessary update information
131
	 * from the administrator.
132
	 * 
133
	 * @param request
134
	 *            the http request information
135
	 * @param response
136
	 *            the http response to be sent back to the client
137
	 */
138
	public void configureDatabase(HttpServletRequest request,
139
			HttpServletResponse response) throws AdminException {
140

    
141
		String processForm = request.getParameter("processForm");
142
		String formErrors = (String) request.getAttribute("formErrors");
143
		HttpSession session = request.getSession();
144

    
145
		if (processForm == null || !processForm.equals("true") || formErrors != null) {
146
			// The servlet configuration parameters have not been set, or there
147
			// were form errors on the last attempt to configure, so redirect to
148
			// the web form for configuring metacat
149

    
150
			try {
151
				// get the current metacat version and the database version. If
152
				// the database version is older that the metacat version, run
153
				// the appropriate scripts to get them synchronized.
154

    
155
				databaseVersion = discoverDBVersion();
156
				MetaCatVersion metacatVersion = SystemUtil.getMetacatVersion();
157
				
158
				session.setAttribute("metacatVersion", MetaCatVersion.getVersionID());
159

    
160
				// if the db version is already the same as the metacat
161
				// version, update metacat.properties
162
				if (databaseVersion.compareTo(metacatVersion) == 0) {
163
					PropertyService.setProperty("configutil.databaseConfigured",
164
							PropertyService.CONFIGURED);
165
				}
166
				
167
				MetaCatVersion metaCatVersion = SystemUtil.getMetacatVersion();
168
				request.setAttribute("metacatVersion", metaCatVersion);
169
				DBVersion dbVersionString = getDBVersion();
170
				request.setAttribute("databaseVersion", dbVersionString);
171
				Vector<String> updateScriptList = getUpdateScripts();
172
				request.setAttribute("updateScriptList", updateScriptList);
173

    
174
				// Forward the request to the JSP page
175
				RequestUtil.clearRequestMessages(request);
176
				RequestUtil.forwardRequest(request, response,
177
						"/admin/database-configuration.jsp");
178
			} catch (GeneralPropertyException gpe) {
179
				throw new AdminException("Problem getting or setting property while " 
180
						+ "initializing system properties page: " + gpe.getMessage());
181
			} catch (IOException ioe) {
182
				throw new AdminException("IO problem while initializing "
183
						+ "system properties page:" + ioe.getMessage());
184
			} catch (ServletException se) {
185
				throw new AdminException("problem forwarding request while "
186
						+ "initializing system properties page: " + se.getMessage());
187
			}  
188
		} else {
189
			// The configuration form is being submitted and needs to be
190
			// processed, setting the properties in the configuration file
191
			// then restart metacat
192

    
193
			// The configuration form is being submitted and needs to be
194
			// processed.
195
			Vector<String> validationErrors = new Vector<String>();
196
			Vector<String> processingSuccess = new Vector<String>();
197

    
198
			try {
199
				// Validate that the options provided are legitimate. Note that
200
				// we've allowed them to persist their entries. As of this point
201
				// there is no other easy way to go back to the configure form
202
				// and
203
				// preserve their entries.
204
				validationErrors.addAll(validateOptions(request));
205

    
206
				upgradeDatabase();
207

    
208
				// Now that the options have been set, change the
209
				// 'databaseConfigured' option to 'true' so that normal metacat
210
				// requests will go through
211
				PropertyService.setProperty("configutil.databaseConfigured",
212
						PropertyService.CONFIGURED);
213

    
214
				// Reload the main metacat configuration page
215
				processingSuccess.add("Database successfully upgraded");
216
				RequestUtil.clearRequestMessages(request);
217
				RequestUtil.setRequestSuccess(request, processingSuccess);
218
				RequestUtil.forwardRequest(request, response,
219
						"/admin?configureType=configure&processForm=false");
220
				// Write out the configurable properties to a backup file
221
				// outside the install directory.
222

    
223
				PropertyService.persistMainBackupProperties(request.getSession()
224
						.getServletContext());
225
			} catch (GeneralPropertyException gpe) {
226
				throw new AdminException("Problem getting or setting property while "
227
						+ "upgrading database: " + gpe.getMessage());
228
			}  catch (IOException ioe) {
229
				throw new AdminException("IO problem while upgrading database: "
230
						 + ioe.getMessage());
231
			} catch (ServletException se) {
232
				throw new AdminException("problem forwarding request while "
233
						+ "upgrading database: " + se.getMessage());
234
			}
235
		}
236
	}
237

    
238

    
239
	/**
240
	 * Performs a status check on the database.
241
	 * 
242
	 * @returns integer representing the status of the database. These can be: 
243
	 * 		-- DB_DOES_NOT_EXIST = 0; 
244
	 *      -- TABLES_DO_NOT_EXIST = 1; 
245
	 *      -- TABLES_EXIST = 2;
246
	 */
247
	public int getDBStatus() throws SQLException, PropertyNotFoundException {
248
		Connection connection = DBUtil.getConnection(PropertyService
249
				.getProperty("database.connectionURI"), PropertyService
250
				.getProperty("database.user"), PropertyService
251
				.getProperty("database.password"));
252

    
253
		if (DBUtil.tableExists(connection, "xml_documents")) {
254
			return TABLES_EXIST;
255
		}
256

    
257
		return TABLES_DO_NOT_EXIST;
258
	}
259

    
260
	/**
261
	 * Get the version of the database as a string
262
	 * 
263
	 * @returns string representing the version of the database.
264
	 */
265
	public DBVersion getDBVersion() throws AdminException {
266

    
267
		// don't even try to search for a database version until system
268
		// properties have been configured
269
		try {
270
		if (!PropertyService.arePropertiesConfigured()) {
271
			throw new AdminException("An attempt was made to get the database version " 
272
					+ "before system properties were configured");
273
		}
274
		} catch (UtilException ue) {
275
			throw new AdminException("Could not determine the database version: " + ue.getMessage());
276
		}
277
		if (databaseVersion == null) {
278
			databaseVersion = discoverDBVersion();
279
		}
280

    
281
		if (databaseVersion == null) {
282
			throw new AdminException("Could not find database version");
283
		}
284

    
285
		return databaseVersion;
286
	}
287

    
288
	/**
289
	 * Try to discover the database version, first by calling
290
	 * getRegisteredDBVersion() to see if the database version is in a table in
291
	 * the database. If not, getUnRegisteredDBVersion() is called to see if we
292
	 * can devine the version by looking for unique changes made by scripts for
293
	 * each version update.
294
	 * 
295
	 * @returns string representing the version of the database, null if none
296
	 *          could be found.
297
	 */
298
	private DBVersion discoverDBVersion() throws AdminException {
299
		try {
300
			int dbStatus = getDBStatus();
301
			if (dbStatus == DB_DOES_NOT_EXIST) {
302
				throw new AdminException(
303
						"Database does not exist for connection"
304
						+ PropertyService.getProperty("database.connectionURI"));
305
			} else if (dbStatus == TABLES_DO_NOT_EXIST) {
306
				databaseVersion = new DBVersion("0.0.0");
307
				return databaseVersion;
308
			}
309

    
310
			databaseVersion = getRegisteredDBVersion();
311
			if (databaseVersion != null) {
312
				return databaseVersion;
313
			}
314

    
315
			databaseVersion = getUnRegisteredDBVersion();
316
			
317
		} catch (SQLException sqle) {
318
			String errorMessage = "SQL error during  database version discovery: "
319
				+ sqle.getMessage();
320
			logMetacat.error(errorMessage);
321
			throw new AdminException(errorMessage);
322
		} catch (PropertyNotFoundException pnfe) {
323
			String errorMessage = "Property not found during  database version discovery: "
324
					+ pnfe.getMessage();
325
			logMetacat.error(errorMessage);
326
			throw new AdminException(errorMessage);
327
		} catch (NumberFormatException nfe) {
328
			throw new AdminException("Bad version format numbering: "
329
					+ nfe.getMessage());
330
		}
331
		
332
		if (databaseVersion == null) {
333
			throw new AdminException("Database version discovery returned null");
334
		}
335
		return databaseVersion;
336
	}
337

    
338
	/**
339
	 * Gets the version of the database from the db_version table. Usually this
340
	 * is the same as the version of the product, however the db version could
341
	 * be more granular if we applied a maintenance patch for instance.
342
	 * 
343
	 * @returns string representing the version of the database.
344
	 */
345
	private DBVersion getRegisteredDBVersion() throws AdminException, SQLException {
346
		String dbVersionString = null;
347
		PreparedStatement pstmt = null;
348

    
349
		try {
350
			// check out DBConnection
351
			Connection connection = 
352
				DBUtil.getConnection(
353
						PropertyService.getProperty("database.connectionURI"),
354
						PropertyService.getProperty("database.user"),
355
						PropertyService.getProperty("database.password"));
356

    
357
			if (!DBUtil.tableExists(connection, "db_version")) {
358
				return null;
359
			}
360

    
361
			pstmt = 
362
				connection.prepareStatement("SELECT version FROM db_version WHERE status = ?");
363

    
364
			// Bind the values to the query
365
			pstmt.setInt(1, VERSION_ACTIVE);
366
			pstmt.execute();
367
			ResultSet rs = pstmt.getResultSet();
368
			boolean hasRows = rs.next();
369
			if (hasRows) {
370
				dbVersionString = rs.getString(1);
371
			}
372
			
373
			if (dbVersionString == null) {
374
				return null;
375
			} 
376
				
377
			return new DBVersion(dbVersionString);
378
			
379
		} catch (SQLException sqle) {
380
			throw new AdminException("Could not run SQL to get registered db version: " 
381
					+ sqle.getMessage());			
382
		} catch (PropertyNotFoundException pnfe) {
383
			throw new AdminException("Could not get property for registered db version: " 
384
					+ pnfe.getMessage());		
385
		} catch (NumberFormatException nfe) {
386
			throw new AdminException("Bad version format numbering: "
387
					+ nfe.getMessage());
388
		} finally {
389
			if (pstmt != null) {
390
				pstmt.close();
391
			}
392
		}
393
	}
394

    
395
	/**
396
	 * Finds the version of the database for a database that does not have a
397
	 * dbVersion table yet. Work backwards with various clues found in update
398
	 * scripts to find the version.
399
	 * 
400
	 * @returns string representing the version of the database.
401
	 */
402
	public DBVersion getUnRegisteredDBVersion() throws AdminException, SQLException {
403
		Connection connection = null;
404
		try {
405
			connection = DBUtil.getConnection(PropertyService
406
					.getProperty("database.connectionURI"), PropertyService
407
					.getProperty("database.user"), PropertyService
408
					.getProperty("database.password"));
409

    
410
			String dbVersionString = null;
411

    
412
			if (is1_9_0(connection)) {
413
				dbVersionString = "1.9.0";
414
			} else if (is1_8_0(connection)) {
415
				dbVersionString = "1.8.0";
416
			} else if (is1_7_0(connection)) {
417
				dbVersionString = "1.7.0";
418
			} else if (is1_6_0(connection)) {
419
				dbVersionString = "1.6.0";
420
			} else if (is1_5_0(connection)) {
421
				dbVersionString = "1.5.0";
422
			} else if (is1_4_0(connection)) {
423
				dbVersionString = "1.4.0";
424
			} else if (is1_3_0(connection)) {
425
				dbVersionString = "1.3.0";
426
			} else if (is1_2_0(connection)) {
427
				dbVersionString = "1.2.0";
428
			}
429

    
430
			if (dbVersionString == null) {
431
				return null;
432
			} else {
433
				return new DBVersion(dbVersionString);
434
			}
435
		} catch (PropertyNotFoundException pnfe) {
436
			throw new AdminException(
437
					"Could not get property for unregistered db version: "
438
							+ pnfe.getMessage());
439
		} catch (NumberFormatException nfe) {
440
			throw new AdminException("Bad version format numbering: "
441
					+ nfe.getMessage());
442
		}
443
	}
444

    
445
	/**
446
	 * Updates the version of the database. Typically this is done in the update
447
	 * scripts that get run when we upgrade the application. This method can be
448
	 * used if you are automating a patch on the database internally.
449
	 * 
450
	 * @returns string representing the version of the database.
451
	 */
452
	public void updateDBVersion() throws SQLException {
453
		DBConnection conn = null;
454
		PreparedStatement pstmt = null;
455
		int serialNumber = -1;
456
		try {
457

    
458
			// check out DBConnection
459
			conn = DBConnectionPool
460
					.getDBConnection("DBAdmin.updateDBVersion()");
461
			serialNumber = conn.getCheckOutSerialNumber();
462
			conn.setAutoCommit(false);
463

    
464
			pstmt = conn.prepareStatement("UPDATE db_version SET status = ?");
465
			pstmt.setInt(1, VERSION_INACTIVE);
466
			pstmt.execute();
467
			pstmt.close();
468

    
469
			pstmt = conn.prepareStatement("INSERT INTO db_version "
470
					+ "(version, status, date_created) VALUES (?,?,?)");
471
			pstmt.setString(1, MetaCatVersion.getVersionID());
472
			pstmt.setInt(2, VERSION_ACTIVE);
473
			pstmt.setTimestamp(3, new Timestamp(new Date().getTime()));
474
			pstmt.execute();
475

    
476
			conn.commit();
477
		} catch (SQLException e) {
478
			conn.rollback();
479
			throw new SQLException("DBAdmin.getDBVersion(). " + e.getMessage());
480
		} catch (PropertyNotFoundException pnfe) {
481
			conn.rollback();
482
			throw new SQLException("DBAdmin.getDBVersion(). " + pnfe.getMessage());
483
		}
484
		finally {
485
			try {
486
				pstmt.close();
487
			} finally {
488
				DBConnectionPool.returnDBConnection(conn, serialNumber);
489
			}
490
		}
491
	}
492

    
493
	/**
494
	 * Validate connectivity to the database. Validation methods return a string
495
	 * error message if there is an issue. This allows the calling code to run
496
	 * several validations and compile the errors into a list that can be
497
	 * displayed on a web page if desired.
498
	 * 
499
	 * @param dbDriver
500
	 *            the database driver
501
	 * @param connection
502
	 *            the jdbc connection string
503
	 * @param user
504
	 *            the user name
505
	 * @param password
506
	 *            the login password
507
	 * @return a string holding error message if validation fails.
508
	 */
509
	public String validateDBConnectivity(String dbDriver, String connection,
510
			String user, String password) {
511
		try {
512
			DBConnection.testConnection(dbDriver, connection, user, password);
513
		} catch (SQLException se) {
514
			return "Invalid database credential was provided: "
515
					+ se.getMessage();
516
		}
517

    
518
		return null;
519
	}
520

    
521
	/**
522
	 * Checks to see if this is a 1.9.0 database schema by looking for the
523
	 * db_version table which was created for 1.9.0. Note, there is no guarantee
524
	 * that this table will not be removed in subsequent versions. You should
525
	 * search for db versions from newest to oldest, only getting to this
526
	 * function when newer versions have not been matched.
527
	 * 
528
	 * @param dbMetaData
529
	 *            the meta data for this database.
530
	 * @returns boolean which is true if table is found, false otherwise
531
	 */
532
	private boolean is1_9_0(Connection connection) throws SQLException {
533
		return DBUtil.tableExists(connection, "db_version");
534
	}
535

    
536
	/**
537
	 * Checks to see if this is a 1.8.0 database schema by looking for the
538
	 * xml_nodes_idx4 index which was created for 1.8.0. Note, there is no
539
	 * guarantee that this index will not be removed in subsequent versions. You
540
	 * should search for db versions from newest to oldest, only getting to this
541
	 * function when newer versions have not been matched.
542
	 * 
543
	 * @param dbMetaData
544
	 *            the meta data for this database.
545
	 * @returns boolean which is true if index is found, false otherwise
546
	 */
547
	private boolean is1_8_0(Connection connection) throws SQLException {
548
		return DBUtil.indexExists(connection, "xml_nodes", "xml_nodes_idx4");
549
	}
550

    
551
	/**
552
	 * Checks to see if this is a 1.7.0 database schema by looking for the
553
	 * xml_documents_idx2 index which was created for 1.7.0. Note, there is no
554
	 * guarantee that this index will not be removed in subsequent versions. You
555
	 * should search for db versions from newest to oldest, only getting to this
556
	 * function when newer versions have not been matched.
557
	 * 
558
	 * @param dbMetaData
559
	 *            the meta data for this database.
560
	 * @returns boolean which is true if index is found, false otherwise
561
	 */
562
	private boolean is1_7_0(Connection connection) throws SQLException {
563
		return DBUtil.indexExists(connection, "xml_documents",
564
				"xml_documents_idx2");
565
	}
566

    
567
	/**
568
	 * Checks to see if this is a 1.6.0 database schema by looking for the
569
	 * identifier table which was created for 1.6.0. Note, there is no guarantee
570
	 * that this table will not be removed in subsequent versions. You should
571
	 * search for db versions from newest to oldest, only getting to this
572
	 * function when newer versions have not been matched.
573
	 * 
574
	 * @param dbMetaData
575
	 *            the meta data for this database.
576
	 * @returns boolean which is true if table is found, false otherwise
577
	 */
578
	private boolean is1_6_0(Connection connection) throws SQLException {
579
		return DBUtil.tableExists(connection, "identifier");
580
	}
581

    
582
	/**
583
	 * Checks to see if this is a 1.5.0 database schema by looking for the
584
	 * xml_returnfield table which was created for 1.5.0. Note, there is no
585
	 * guarantee that this table will not be removed in subsequent versions. You
586
	 * should search for db versions from newest to oldest, only getting to this
587
	 * function when newer versions have not been matched.
588
	 * 
589
	 * @param dbMetaData
590
	 *            the meta data for this database.
591
	 * @returns boolean which is true if table is found, false otherwise
592
	 */
593
	private boolean is1_5_0(Connection connection) throws SQLException {
594
		return DBUtil.tableExists(connection, "xml_returnfield");
595
	}
596

    
597
	/**
598
	 * Checks to see if this is a 1.4.0 database schema by looking for the
599
	 * access_log table which was created for 1.4.0. Note, there is no guarantee
600
	 * that this table will not be removed in subsequent versions. You should
601
	 * search for db versions from newest to oldest, only getting to this
602
	 * function when newer versions have not been matched.
603
	 * 
604
	 * @param dbMetaData
605
	 *            the meta data for this database.
606
	 * @returns boolean which is true if table is found, false otherwise
607
	 */
608
	private boolean is1_4_0(Connection connection) throws SQLException {
609
		return DBUtil.tableExists(connection, "access_log");
610
	}
611

    
612
	/**
613
	 * Checks to see if this is a 1.3.0 database schema by looking for the
614
	 * xml_accesssubtree table which was created for 1.3.0. Note, there is no
615
	 * guarantee that this table will not be removed in subsequent versions. You
616
	 * should search for db versions from newest to oldest, only getting to this
617
	 * function when newer versions have not been matched.
618
	 * 
619
	 * @param dbMetaData
620
	 *            the meta data for this database.
621
	 * @returns boolean which is true if table is found, false otherwise
622
	 */
623
	private boolean is1_3_0(Connection connection) throws SQLException {
624
		return DBUtil.tableExists(connection, "xml_accesssubtree");
625
	}
626

    
627
	/**
628
	 * Checks to see if this is a 1.2.0 database schema by looking for the
629
	 * datareplicate column which was created on the xml_replication table for
630
	 * 1.2.0. Note, there is no guarantee that this column will not be removed
631
	 * in subsequent versions. You should search for db versions from newest to
632
	 * oldest, only getting to this function when newer versions have not been
633
	 * matched.
634
	 * 
635
	 * @param dbMetaData
636
	 *            the meta data for this database.
637
	 * @returns boolean which is true if column is found, false otherwise
638
	 */
639
	private boolean is1_2_0(Connection connection) throws SQLException {
640
		return DBUtil.columnExists(connection, "xml_replication",
641
				"datareplicate");
642
	}
643

    
644
	/**
645
	 * Creates a list of database update script names by looking at the database
646
	 * version and the metacat version and then getting any script that is
647
	 * inbetween the two (inclusive of metacat version).
648
	 * 
649
	 * @returns a Vector of Strings holding the names of scripts that need to be
650
	 *          run to get the database updated to this version of metacat
651
	 * 
652
	 */
653
	public Vector<String> getUpdateScripts() throws AdminException {
654
		Vector<String> updateScriptList = new Vector<String>();
655
		String sqlFileLocation = null;
656
		String databaseType = null;
657
		MetaCatVersion metaCatVersion = null; 
658
		
659
		// get the location of sql scripts
660
		String sqlSuffix = ".sql";
661
		try {
662
			metaCatVersion = SystemUtil.getMetacatVersion();
663
			sqlFileLocation = SystemUtil.getSQLDir();
664
			databaseType = PropertyService.getProperty("database.type");
665
		} catch (PropertyNotFoundException pnfe) {
666
			throw new AdminException("Could not get property while trying " 
667
					+ "to retrieve database update scripts: " + pnfe.getMessage());
668
		}
669
		// for postgres, we only want scripts that end in -postgres.sql
670
		if (databaseType.equals("PostgreSQL")) {
671
			sqlSuffix = "-postgres.sql";
672
		}
673

    
674
		// if either of these is null, we don't want to do anything.  Just 
675
		// return an empty list.
676
		if (metaCatVersion == null || databaseVersion == null) {
677
			return updateScriptList;
678
		}
679

    
680
		// go through all the versions that the the software went through and 
681
		// figure out which ones need to be applied to the database	
682
		for (DBVersion nextVersion : versionSet) {
683
			Vector<String> versionUpdateScripts = nextVersion
684
					.getUpdateScripts();
685
			
686
			// if the database version is 0.0.0, it is new.
687
			// apply all scripts.
688
			if (databaseVersion.getVersionString().equals("0.0.0")
689
					&& nextVersion.getVersionString().equals("0.0.0")) {
690
				for (String versionUpdateScript : versionUpdateScripts) {
691
					updateScriptList.add(sqlFileLocation + FileUtil.getFS()
692
							+ versionUpdateScript + sqlSuffix);
693
				}
694
				return updateScriptList;
695
			}
696

    
697
			// add every update script that is > than the db version
698
			// but <= to the metacat version to the update list.
699
			if (nextVersion.compareTo(databaseVersion) > 0
700
					&& nextVersion.compareTo(metaCatVersion) <= 0
701
					&& nextVersion.getUpdateScripts() != null) {
702
				for (String versionUpdateScript : versionUpdateScripts) {
703
					updateScriptList.add(sqlFileLocation + FileUtil.getFS()
704
							+ versionUpdateScript + sqlSuffix);
705
				}
706
			}
707
		}
708

    
709
		// this should now hold all the script names that need to be run
710
		// to bring the database up to date with this version of metacat
711
		return updateScriptList;
712
	}
713

    
714
	/**
715
	 * Iterates through the list of scripts that need to be run to upgrade
716
	 * the database and calls runSQLFile on each.
717
	 */
718
	public void upgradeDatabase() throws AdminException {
719
		try {
720
			// get a list of the script names that need to be run
721
			Vector<String> updateScriptList = getUpdateScripts();
722

    
723
			// call runSQLFile on each
724
			for (String updateScript : updateScriptList) {
725
				runSQLFile(updateScript);
726
			}
727

    
728
			// update the db version to be the metacat version
729
			databaseVersion = new DBVersion(SystemUtil.getMetacatVersion().getVersionString());
730
		} catch (SQLException sqle) {
731
			throw new AdminException("SQL error when running upgrade scripts: "
732
					+ sqle.getMessage());
733
		} catch (PropertyNotFoundException pnfe) {
734
			throw new AdminException("SQL error when running upgrade scripts: "
735
					+ pnfe.getMessage());
736
		}catch (NumberFormatException nfe) {
737
			throw new AdminException("Bad version format numbering: "
738
					+ nfe.getMessage());
739
		}
740
	}
741

    
742
	/**
743
	 * Runs the commands in a sql script. Individual commands are loaded into a
744
	 * string vector and run one at a time.
745
	 * 
746
	 * @param sqlFileName
747
	 *            the name of the file holding the sql statements that need to
748
	 *            get run.
749
	 */
750
	public void runSQLFile(String sqlFileName) throws AdminException, SQLException {
751

    
752
		// if update file does not exist, do not do the update.
753
		if (FileUtil.getFileStatus(sqlFileName) < FileUtil.EXISTS_READABLE) {
754
			throw new AdminException("Could not read sql update file: "
755
					+ sqlFileName);
756
		}
757

    
758
		Connection connection = null;
759
		try {
760
			connection = DBUtil.getConnection(PropertyService
761
					.getProperty("database.connectionURI"), PropertyService
762
					.getProperty("database.user"), PropertyService
763
					.getProperty("database.password"));
764
			connection.setAutoCommit(false);
765

    
766
			// load the sql from the file into a vector of individual statements
767
			// and execute them.
768
			logMetacat.debug("processing File: " + sqlFileName);
769
			Vector<String> sqlCommands = loadSQLFromFile(sqlFileName);
770
			for (String sqlStatement : sqlCommands) {
771
				Statement statement = connection.createStatement();
772
				logMetacat.debug("executing sql: " + sqlStatement);
773
				statement.execute(sqlStatement);
774
			}
775
			connection.commit();
776
			
777
		} catch (IOException ioe) {
778
			throw new AdminException("Could not read SQL file" 
779
					+ ioe.getMessage());
780
		} catch (PropertyNotFoundException pnfe) {
781
			throw new AdminException("Could not find property to run SQL file" 
782
					+ pnfe.getMessage());
783
		} catch (SQLException sqle) {
784
			if (connection != null) {
785
				connection.rollback();
786
			}
787
			throw sqle;
788
		} finally {
789
			if (connection != null) {
790
				connection.close();
791
			}
792
		}
793
	}
794

    
795
	/**
796
	 * Very basic utility to read sql from a file and return a vector of the
797
	 * individual sql statements. This ignores any line that starts with /* or *.
798
	 * It strips anything following --. Sql is parsed by looking for lines that
799
	 * start with one of the following identifiers: INSERT, UPDATE, ALTER,
800
	 * CREATE, DROP, BEGIN and COMMIT. It then assumes that everything until the
801
	 * line that ends with ; is part of the sql, excluding comments.
802
	 * 
803
	 * @param sqlFileName
804
	 *            the name of the file to read.
805
	 * @return a vector holding the individual sql statements.
806
	 */
807
	public Vector<String> loadSQLFromFile(String sqlFileName)
808
			throws IOException {
809

    
810
		// this will end up holding individual sql statements
811
		Vector<String> sqlCommands = new Vector<String>();
812

    
813
		FileInputStream fin = null;
814
		try {
815
			fin = new FileInputStream(sqlFileName);
816

    
817
			BufferedReader reader = new BufferedReader(new InputStreamReader(
818
					fin));
819

    
820
			// Read in file
821
			String fileLine;
822
			while ((fileLine = reader.readLine()) != null) {
823
				String trimmedLine = fileLine.trim();
824

    
825
				// get the first word on the line
826
				String firstWord = trimmedLine;
827
				if (trimmedLine.indexOf(' ') > 0) {
828
					firstWord = trimmedLine.substring(0, trimmedLine
829
							.indexOf(' '));
830
				}
831
				if (firstWord.endsWith(";")) {
832
					firstWord = firstWord.substring(0, firstWord.indexOf(';'));
833
				}
834

    
835
				// if the first word is a known sql command, start creating a
836
				// sql statement.
837
				if (sqlCommandSet.contains(firstWord.toUpperCase())) {
838
					String sqlStatement = trimmedLine;
839

    
840
					// if this ends with ; it is a single line statement. Add to
841
					// command vector and process next line.
842
					if (trimmedLine.endsWith(";")) {
843
						sqlCommands.add(sqlStatement);
844
						continue;
845
					}
846

    
847
					// keep reading lines until we find one that is not a
848
					// comment and ends with ;
849
					String innerLine;
850
					while ((innerLine = reader.readLine()) != null) {
851
						String trimmedInnerLine = innerLine.trim();
852
						// ignore comments and empty lines
853
						if (trimmedInnerLine.matches("^$")
854
								|| trimmedInnerLine.matches("^\\*.*")
855
								|| trimmedInnerLine.matches("/\\*.*")) {
856
							continue;
857
						}
858

    
859
						// get rid of any "--" comments at the end of the line
860
						if (trimmedInnerLine.indexOf("--") >= 0) {
861
							trimmedInnerLine = trimmedInnerLine.substring(0,
862
									trimmedInnerLine.indexOf("--")).trim();
863
						}
864
						sqlStatement += " " + trimmedInnerLine;
865
						if (trimmedInnerLine.endsWith(";")) {
866
							sqlCommands.add(sqlStatement);
867
							break;
868
						}
869
					}
870
				}
871
			}
872
		} finally {
873
			// Close our input stream
874
			fin.close();
875
		}
876

    
877
		return sqlCommands;
878
	}
879

    
880
	/**
881
	 * Validate the most important configuration options submitted by the user.
882
	 * 
883
	 * @return a vector holding error message for any fields that fail
884
	 *         validation.
885
	 */
886
	protected Vector<String> validateOptions(HttpServletRequest request) {
887
		Vector<String> errorVector = new Vector<String>();
888

    
889
		// TODO MCD validate options.
890

    
891
		return errorVector;
892
	}
893
}
(3-3/8)