View Javadoc

1   /* $Id: DatabaseConnector.java,v 1.22 2006/01/12 15:13:08 psionides Exp $ */
2   
3   package net.sourceforge.jdbdump.connect;
4   
5   import java.sql.Connection;
6   import java.sql.DatabaseMetaData;
7   import java.sql.DriverManager;
8   import java.sql.ResultSet;
9   import java.sql.SQLException;
10  import java.util.Vector;
11  
12  import org.apache.log4j.Logger;
13  
14  import net.sourceforge.jdbdump.dump.Column;
15  import net.sourceforge.jdbdump.dump.Dump;
16  import net.sourceforge.jdbdump.dump.Table;
17  
18  /***
19   * A DatabaseConnector is an object handling connection to the database which will be backed up.
20   * It manages the Connection object, performs all queries and updates and creates a Dump object.
21   * It is the only point of entry to the database in the system, so no other object has to run
22   * any queries, updates, etc.
23   * <br /><br />
24   * This is an abstract class, because it is impossible to write a single universal method which
25   * will make a complete backup of any possible database. Those parts of the backup which are
26   * characteristic only to some database engines will have to be done in specific connectors.
27   * A generic DatabaseConnector can only supply default ways of doing some backup parts, because
28   * many of them will be identical for most database types.
29   * <br /><br />
30   * Connectors are created using a DatabaseConnectorFactory. A dump object uses the connector with
31   * which it was created to download the tables' data, so you may disconnect() a connector only when
32   * all the work is finished. 
33   * <br /><br />
34   * Example code:
35   * <pre>
36   * DatabaseConnectorFactory factory = DatabaseConnectorFactory.getInstance();
37   * String[] connectorTypes = factory.listPlugins();
38   * if (connectorTypes.length > 0) {
39   *     DatabaseConnector connector = factory.createConnector(connectorTypes[0]);
40   *     connector.connect(url, login, pass);
41   *     Dump dump = connector.dump();
42   *     // process the dump and its data...
43   *     connector.disconnect();
44   * } else {
45   *     // error - no connectors
46   * }
47   * </pre>
48   * 
49   * @author jsuder
50   */
51  
52  public abstract class DatabaseConnector {
53  	
54  	/*** An object providing direct access to the database through JDBC. */
55  	protected Connection connection;
56  
57  	/***
58  	 * An object providing access to information about database structure,
59  	 * for example a list of all tables with their attributes, columns, etc.  
60  	 */
61  	protected DatabaseMetaData meta;
62  	
63  	/*** A log4j logger for this class. */
64  	private static Logger logger = Logger.getLogger(DatabaseConnector.class);
65  	
66  	/***
67  	 * Opens a connection to the database, using the provided connection data.
68  	 * @param url a correct connection URL
69  	 * @param user user's login in the database
70  	 * @param pass user's password
71  	 * @throws SQLException if the connection is not possible
72  	 * @throws ClassNotFoundException if there is a problem with loading a driver
73  	 */
74  
75  	public abstract void connect(String url, String user, String pass)
76  	throws SQLException, ClassNotFoundException;
77  	
78  	/***
79  	 * Opens a connection to the database, using the provided connection data
80  	 * and the specified JDBC driver.
81  	 * 
82  	 * @param url a correct connection URL
83  	 * @param user user's login in the database
84  	 * @param pass user's password
85  	 * @param driver full name of the JDBC driver class 
86  	 * @throws SQLException if the connection is not possible
87  	 * @throws ClassNotFoundException if there is a problem with loading a driver
88  	 */
89  
90  	protected void connect(String url, String user, String pass, String driver)
91  	throws SQLException, ClassNotFoundException {
92  		logger.info("connect(): Initializing driver " + driver + ".");
93  		Class.forName(driver);
94  		logger.info("connect(): Connecting to database " + url + ".");
95  		connection = DriverManager.getConnection(url, user, pass);
96  		meta = connection.getMetaData();
97  	}
98  
99  	/***
100 	 * Closes the database connection. 
101 	 */
102 	
103 	public void disconnect() {
104 		try {
105 			logger.info("disconnect(): Disconnecting from database.");
106 			connection.close();
107 			connection = null;
108 			meta = null;
109 		} catch (SQLException e) {
110 			logger.warn("disconnect(): Error while disconnecting: " + e);
111 		}
112 	}
113 
114 	/***
115 	 * Creates a backup of the structure of the entire database. It doesn't backup
116 	 * any data - this has to be done after the dump is created, using methods in
117 	 * Table class.
118 	 * 
119 	 * @return a Dump object containing information about database structure
120 	 * @throws SQLException if the backup process is interrupted by a critical error
121 	 */
122 	
123 	public abstract Dump dump() throws SQLException;
124 
125 	/***
126 	 * Restores the structure of the database from a Dump object loaded from a previously
127 	 * created backup file. It doesn't restore any data - this has to be done after the
128 	 * dump is restored, using methods in Table class.
129 	 * 
130 	 * @param dump a Dump object containing information about database structure
131 	 * @throws SQLException if the restore process is interrupted by a critical error
132 	 */
133 
134 	public abstract void restore(Dump dump) throws SQLException;
135 	
136 	/***
137 	 * Creates a database-specific JDBC connection url (usually, although not always,
138 	 * it has the form: jdbc:dbmstype://host:port/database).
139 	 * @param data an object providing all the data necessary to open the connection 
140 	 * @return a connection url string generated from the data 
141 	 */
142 
143 	public abstract String createURL(DatabaseConnectionData data);
144 	
145 	/***
146 	 * Downloads the structure of all tables in the database. This is a helper method
147 	 * which can be used by inheriting connectors in their dump() method, if they don't
148 	 * have any specific way of doing this.
149 	 * @param dump a dump to which the downloaded tables should be added
150 	 * @throws SQLException if the backup process is interrupted by a critical error
151 	 */
152 
153 	protected void dumpTables(Dump dump) {
154 		Vector<Table> tables = dump.getTables();
155 		ResultSet rsTables;
156 		try {
157 			rsTables = meta.getTables(null, "public", null, new String[] {"TABLE"});
158 			while (rsTables.next()) {
159 				Table t = new Table(rsTables.getString("TABLE_NAME"), dump);
160 				dumpTable(t);
161 				tables.add(t);
162 			}
163 		} catch (SQLException e) {
164 			logger.error("Couldn't get information about tables from the metadata.");
165 			return;
166 		}
167 	}
168 		
169 	/***
170 	 * Downloads the structure of a single table from the database. This is a helper method
171 	 * which can be used by inheriting connectors in their dump() method, if they don't
172 	 * have any specific way of doing this.
173 	 * @param table a chosen table in the dump, to which the downloaded information should be assigned 
174 	 * @throws SQLException if the backup process is interrupted by a critical error
175 	 */
176 
177 	protected void dumpTable(Table table) {
178 		Vector<Column> columns = table.getColumns();
179 		try {
180 			ResultSet rsColumns = meta.getColumns(null, null, table.getName(), null);
181 		
182 			while (rsColumns.next()) {
183 				Column c = new Column(rsColumns.getString("COLUMN_NAME"));
184 				dumpColumn(c, rsColumns);
185 				columns.add(c);
186 			}
187 		
188 			dumpTablePrimaryKeys(table);
189 			dumpTableForeignKeys(table);
190 		} catch (SQLException e) {
191 			logger.error("Couldn't get information about a table.");
192 			return;
193 		}
194 	}
195 
196 	/***
197 	 * Downloads the table's primary key from the database. This method is called in dumpTable()
198 	 * after it retrieves all the columns.
199 	 * <br /><br />
200 	 * The way in which primary keys are specified differs between various database engines,
201 	 * so by default this method does nothing; if a specific DatabaseConnector wants in to work
202 	 * for its kind of database, it must override this method and provide its implementation.
203 	 * 
204 	 * @param table a table which should be checked for primary keys
205 	 * @throws SQLException if the backup process is interrupted by a critical error
206 	 */
207 	
208 	protected void dumpTablePrimaryKeys(Table table) throws SQLException {
209 		// implemented by derived connectors if they need that
210 	}
211 
212 	/***
213 	 * Downloads the table's foreign (imported) keys from the database. This method is called
214 	 * in dumpTable() after it retrieves all the columns.
215 	 * <br /><br />
216 	 * The way in which foreign keys are specified differs between various database engines,
217 	 * so by default this method does nothing; if a specific DatabaseConnector wants in to work
218 	 * for its kind of database, it must override this method and provide its implementation.
219 	 * 
220 	 * @param table a table which should be checked for foreign keys
221 	 * @throws SQLException if the backup process is interrupted by a critical error
222 	 */
223 
224 	protected void dumpTableForeignKeys(Table table) throws SQLException {
225 		// implemented by derived connectors if they need that
226 	}
227 		
228 	/***
229 	 * Downloads the structure of a single table column from the database. This method is called
230 	 * by dumpTable(). If a specific DatabaseConnector wants to use default dumpTables(),
231 	 * but needs a non-default way of handling columns (if it, for example, needs to
232 	 * handle in a special way some specific data types), it should override this method (the
233 	 * overridden version will be called by dumpTables()).
234 	 * 
235 	 * @param column a column which should have its parameters downloaded
236 	 * @param rsColumns a ResultSet returned by DatabaseMetaData.getColumns(),
237 	 *     set on this column's record
238 	 * @throws SQLException if the backup process is interrupted by a critical error
239 	 */
240 
241 	protected void dumpColumn(Column column, ResultSet rsColumns) throws SQLException {
242 		column.setLength(rsColumns.getInt("COLUMN_SIZE"));
243 		column.setType(rsColumns.getString("TYPE_NAME"));
244 		column.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS"));
245 		column.setNullable(rsColumns.getInt("NULLABLE"));
246 		column.setNullable2(rsColumns.getString("IS_NULLABLE"));
247 		column.setDefault(rsColumns.getString("COLUMN_DEF"));
248 		column.setRemarks(rsColumns.getString("REMARKS"));
249 	}
250 
251 	/***
252 	 * A debugging method used to print the entire result set (all rows,
253 	 * all fields in them, also with field names). It can be used e.g. to check
254 	 * what information can be retrieved from ResultSets returned by various
255 	 * DatabaseMetaData methods.
256 	 * 
257 	 * @param rs the ResultSet to be printed
258 	 * @throws SQLException if there is something wrong with the resultset
259 	 */
260 	
261 	public static void printResultSet(ResultSet rs) throws SQLException {
262 		java.sql.ResultSetMetaData rsmd = rs.getMetaData();
263 		int cols = rsmd.getColumnCount();
264 		int row = 1;
265 		logger.debug(cols+"");
266 		while (rs.next()) {
267 			logger.debug("ResultSet row #" + (row++));
268 			for (int i=1; i<=cols; i++) {
269 				logger.debug("- " + rsmd.getColumnName(i) + ": " + rs.getString(i));
270 			}
271 		}
272 	}
273 	
274 	/***
275 	 * Prepares the given table for downloading its data. That means, it runs a query
276 	 * like "SELECT * FROM tablename" and stores the received result set.
277 	 * @param tableName name of the table from which data should be downloaded
278 	 * @throw SQLException if the resultset can't be returned
279 	 */
280 	public abstract void initializeTableData(String tableName) throws SQLException;
281 
282 	/***
283 	 * Downloads one record of data from the previously initialized table in database.
284 	 * @return next record from the table, or null if there are no more records
285 	 * @throws SQLException if data record can't be downloaded because of a connection error
286 	 *     or an error in the database
287 	 */
288 	public abstract String[] getTableDataLine() throws SQLException; 
289 }