1
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
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
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 }