DBVIEW
|
00001 /* 00002 DbView - Graph Visualization 00003 Copyright (C) 2012 Denis BEURIVE 00004 00005 This program is free software: you can redistribute it and/or modify 00006 it under the terms of the GNU General Public License as published by 00007 the Free Software Foundation, either version 3 of the License, or 00008 (at your option) any later version. 00009 00010 This program is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 GNU General Public License for more details. 00014 00015 You should have received a copy of the GNU General Public License 00016 along with this program. If not, see <http://www.gnu.org/licenses/>. 00017 */ 00018 00019 /** 00020 * @author Denis Beurive 00021 */ 00022 00023 package org.dbview.input_addons; 00024 00025 import java.sql.Connection; 00026 import java.sql.DriverManager; 00027 import java.sql.DatabaseMetaData; 00028 import java.sql.ResultSet; 00029 import java.util.*; 00030 import org.jdom.*; 00031 import org.dbview.db.structure.*; 00032 import org.dbview.resources.*; 00033 00034 /** 00035 * <p>This class implements the database loader adaptor.</p> 00036 * <p>Please note that the exploration of the database relies on JDBC type 4.</p> 00037 * <p>Child classes may override the methods, if a specific database needs specific procedures.</p> 00038 * @author Denis Beurive 00039 */ 00040 public abstract class AbstractLoader 00041 { 00042 /** 00043 * Handler to the database's connection. 00044 */ 00045 private Connection __connection = null; 00046 00047 /** 00048 * This attribute contains the "meta data" for the current database. 00049 * @see JDBC for details. 00050 */ 00051 private DatabaseMetaData __db_meta = null; 00052 00053 /** 00054 * This method returns an instance of a soft foreign key detector (if it is defined in the profile). 00055 * @param in_profile Profile used to open a connexion to the database through the input add-on. 00056 * @return The method returns an instance of a soft foreign key detector, or null if no detector is defined. 00057 */ 00058 protected abstract AbstractSotfForeignKeyDetector _getSoftForeignKeyDetector_(Element in_profile) throws Exception; 00059 00060 /** 00061 * This method returns the URL used to open a connection to the database. 00062 * @param in_data XML element that contains the data required to open the connection. 00063 * @return The method returns the URL used to open a connection to the database. 00064 * @throws Exception 00065 */ 00066 protected abstract String _getUrl_(Element in_data) throws Exception; 00067 00068 /** 00069 * This method returns the list of all tables in the database. 00070 * @param in_connection Connection to the database. 00071 * @param in_db_meta Meta data associated to the database. 00072 * @return The method returns the list of all tables in the database. 00073 * @throws Exception 00074 */ 00075 protected ArrayList<String> _tables(Connection in_connection, DatabaseMetaData in_db_meta) throws Exception 00076 { 00077 ArrayList<String> tables = new ArrayList<String>(); 00078 ResultSet tbl = in_db_meta.getTables(in_connection.getCatalog(), "%", "%", null); 00079 while (tbl.next()) { tables.add(tbl.getString("TABLE_NAME")); } 00080 return tables; 00081 } 00082 00083 /** 00084 * This method returns the list of all indexes for a given table. 00085 * It also set the table's list of indexes. 00086 * @param in_connection Connection to the database. 00087 * @param in_db_meta Meta data associated to the database. 00088 * @param in_table The table. 00089 * @return The method returns the list of all indexes for the given table. 00090 * @throws Exception 00091 */ 00092 protected Hashtable<String, DbIndex> _indexes(Connection in_connection, DatabaseMetaData in_db_meta, Table in_table) throws Exception 00093 { 00094 String table_name = in_table.getName(); 00095 Hashtable<String, DbIndex> indexes = new Hashtable<String, DbIndex>(); 00096 00097 ResultSet idxs = in_db_meta.getIndexInfo(in_connection.getCatalog(), "%", table_name, false, false); 00098 while (idxs.next()) 00099 { 00100 String field_name = idxs.getString("COLUMN_NAME"); 00101 Boolean unique = idxs.getBoolean("NON_UNIQUE") ? Boolean.FALSE : Boolean.TRUE; 00102 String index_name = idxs.getString("INDEX_NAME"); 00103 00104 // System.out.println(">>>>> " + index_name + " " + field_name); 00105 00106 // Is the index already listed? 00107 if (indexes.containsKey(index_name)) 00108 { 00109 // The index is already listed. We just add a new field to it. 00110 DbIndex index = indexes.get(index_name); 00111 index.addField(in_table.getField(field_name)); 00112 } 00113 else 00114 { 00115 // The index is not already listed. 00116 DbIndex index = new DbIndex(table_name, unique, index_name); 00117 index.addField(in_table.getField(field_name)); 00118 indexes.put(index_name, index); 00119 } 00120 } 00121 in_table.setIndexes(indexes); 00122 return indexes; 00123 } 00124 00125 /** 00126 * This method returns the primary key for a given table. 00127 * @param in_connection Connection to the database. 00128 * @param in_db_meta Meta data associated to the database. 00129 * @param in_table The table. 00130 * @return <ul> 00131 * <li>The method returns the name of the primary key, if the table defines a primary key.</li> 00132 * <li>If the method does not define any primary key, then the method returns the value null.</li> 00133 * </ul> 00134 * @throws Exception 00135 */ 00136 protected ArrayList<String> _primary(Connection in_connection, DatabaseMetaData in_db_meta, Table in_table) throws Exception 00137 { 00138 ArrayList<String> fields = new ArrayList<String>(); 00139 ResultSet pks = in_db_meta.getPrimaryKeys(in_connection.getCatalog(), "%", in_table.getName()); 00140 while (pks.next()) { fields.add(pks.getString("COLUMN_NAME")); } 00141 return fields; 00142 } 00143 00144 /** 00145 * This method returns the list of all foreign keys for a given table. 00146 * @param in_connection Connection to the database. 00147 * @param in_db_meta Meta data associated to the database. 00148 * @param in_table The table. 00149 * @return The method returns the list of all foreign keys for the given table. 00150 * Each element of the returned list is an array that contains 2 elements. 00151 * <ul> 00152 * <li>First element: The name of the foreign key, within the given table.</li> 00153 * <li>Second element: The name of the field, within the target table.</li> 00154 * </ul> 00155 * @throws Exception 00156 */ 00157 protected ArrayList<DbForeignKey> _foreign(Connection in_connection, DatabaseMetaData in_db_meta, Table in_table) throws Exception 00158 { 00159 String table_name = in_table.getName(); 00160 ArrayList<DbForeignKey> foreign_keys = new ArrayList<DbForeignKey>(); 00161 ResultSet fks = in_db_meta.getImportedKeys(in_connection.getCatalog(), "%", table_name); 00162 00163 while (fks.next()) 00164 { 00165 String field_name = fks.getString("FKCOLUMN_NAME"); 00166 String target_field_name = fks.getString("PKCOLUMN_NAME"); 00167 String target_table_name = fks.getString("PKTABLE_NAME"); 00168 foreign_keys.add(new DbForeignKey(table_name, field_name, target_table_name, target_field_name)); 00169 } 00170 00171 return foreign_keys; 00172 } 00173 00174 /** 00175 * This method returns the list of all fields for a given table. 00176 * @param in_connection Connection to the database. 00177 * @param in_db_meta Meta data associated to the database. 00178 * @param in_table The table. 00179 * @return The method returns the list of all fields for the given table. 00180 * @throws Exception 00181 */ 00182 protected ArrayList<String> _fields(Connection in_connection, DatabaseMetaData in_db_meta, Table in_table) throws Exception 00183 { 00184 ArrayList<String> fields = new ArrayList<String>(); 00185 ResultSet keys = in_db_meta.getColumns(in_connection.getCatalog(), "%", in_table.getName(), "%"); 00186 while (keys.next()) { fields.add(keys.getString("COLUMN_NAME")); } 00187 return fields; 00188 } 00189 00190 /** 00191 * This method returns the list of fields that can be null for a given table. 00192 * @param in_connection Connection to the database. 00193 * @param in_db_meta Meta data associated to the database. 00194 * @param in_table The table. 00195 * @return Thie method returns the list of fields that can be null. 00196 * @throws Exception 00197 */ 00198 protected ArrayList<String> _null(Connection in_connection,DatabaseMetaData in_db_meta, Table in_table) throws Exception 00199 { 00200 ArrayList<String> fields = new ArrayList<String>(); 00201 ResultSet keys = in_db_meta.getColumns(in_connection.getCatalog(), "%", in_table.getName(), "%"); 00202 while (keys.next()) 00203 { 00204 if (0 == keys.getString("IS_NULLABLE").compareTo("YES")) 00205 { fields.add(keys.getString("COLUMN_NAME")); } 00206 } 00207 return fields; 00208 } 00209 00210 /** 00211 * This method loads information from a given database and returns a model of the database. 00212 * @param in_profile Profile associated to the database (<code><data>...</data></code>). 00213 * @return The method returns a model of the database. 00214 * @throws Exception 00215 */ 00216 public Database load(Element in_profile) throws Exception 00217 { 00218 ArrayList<String> tables = null; 00219 Database database = null; 00220 String URL = null; 00221 00222 // Get the URL used to open the connection to the database. 00223 URL = this._getUrl_(in_profile); 00224 // System.out.println(URL); 00225 00226 // Connect to the database. 00227 this.__connection = DriverManager.getConnection(URL); 00228 00229 // Get meta data. 00230 // The metadata includes information about the database's tables, its supported SQL grammar, its stored procedures, the capabilities of this connection, and so on. 00231 this.__db_meta = this.__connection.getMetaData(); 00232 00233 // Create the database data structure. 00234 database = new Database(this.__connection.getCatalog()); 00235 00236 // Get the names off all tables. 00237 tables = this._tables(this.__connection, this.__db_meta); 00238 00239 // For each table, get the list of primary keys and indexes. 00240 for (String table_name: tables) 00241 { 00242 // Create the table, within the database. 00243 Table table = new Table(database, table_name); 00244 00245 // Add all fields in the table. 00246 for (String field_name: this._fields(this.__connection, this.__db_meta, table)) 00247 { 00248 @SuppressWarnings("unused") 00249 Field field = new Field(table, field_name); 00250 } 00251 00252 // Set the primary keys, if exists. 00253 ArrayList<String> primaries = this._primary(this.__connection, this.__db_meta, table); 00254 if (null != primaries) 00255 { 00256 for (String primary: primaries) 00257 { table.getField(primary).isAPrimaryKey(); } 00258 } 00259 00260 // Set all fields' properties that must be set. 00261 // Indexes set : Indexes that are composed by one and only one field. 00262 this._indexes(this.__connection, this.__db_meta, table); 00263 Hashtable<String, DbIndex> unique_indexes = table.getSimpleIndexes(); 00264 00265 for (Enumeration<String> e = unique_indexes.keys(); e.hasMoreElements(); ) 00266 { 00267 String index_name = e.nextElement(); 00268 DbIndex index = unique_indexes.get(index_name); 00269 String field_name = index.getField(0).getName(); 00270 if (index.unique) { table.getField(field_name).isAUniqueIndex(); } 00271 else { table.getField(field_name).isAMultipleIndex(); } 00272 } 00273 } 00274 00275 // For each table, get the list of hard foreign keys, and the list of null fields. 00276 for (String table_name: tables) 00277 { 00278 Table table = database.getTable(table_name); 00279 00280 // Set all foreign keys. 00281 for (DbForeignKey key: this._foreign(this.__connection, this.__db_meta, table)) 00282 { 00283 // Get the "reference" table. 00284 // If the reference table is not listed, we create a dead join. 00285 Table reference_table = database.getTable(key.reference_table_name); 00286 00287 if (null == reference_table) 00288 { 00289 // This is a dead foreign key. 00290 table.getField(key.field_name).isADeadforeignKey(key.reference_table_name, key.reference_field_name); 00291 // System.out.println("Found a DFK: " + key.getFullName() + " => " + key.reference_table_name); 00292 continue; 00293 // A foreign key may be in a distinct database. 00294 // throw new Exception("Unexpected error : the database seems to be invalid. Found a foreign key (\"" + key.field_name + "\") witch reference's table (\"" + key.reference_table_name + "\") does not exist!"); 00295 } 00296 00297 // Get the "reference" field. 00298 // If the reference field is not listed, we create a dead join. 00299 Field reference_field = reference_table.getField(key.reference_field_name); 00300 if (null == reference_field) 00301 { 00302 // This is a dead foreign key. 00303 table.getField(key.field_name).isADeadforeignKey(key.reference_table_name, key.reference_field_name); 00304 // System.out.println("Found a DFK: " + key.getFullName() + " => " + key.reference_table_name); 00305 continue; 00306 // A foreign key may be in a distinct database. 00307 // throw new Exception("Unexpected error : the database seems to be invalid. Found a foreign key (\"" + key.field_name + "\") witch reference's field (\"" + key.reference_table_name + "." + key.reference_field_name + "\") does not exist!"); 00308 } 00309 00310 // Debug reès utile: 00311 // Field fk = table.getField(key.field_name); 00312 // System.out.println(" (FK) " + key.getFullName() + " -> " + target_field.getFullName()); 00313 // System.out.println(" table.getField(" + key.getFullName() + ").isAforeignKey(" + target_field.getFullName() + ")");; 00314 // System.out.println(" Is the reference key " + target_field.getFullName() + " a primary key? " + target_field.isPrimaryKey().toString()); 00315 // System.out.println(" Is the foreign key " + fk.getFullName() + " a primary key? " + fk.isPrimaryKey().toString()); 00316 // System.out.println(" Is the foreign key " + fk.getFullName() + " (already) a foreign key? " + fk.isForeignKey().toString()); 00317 00318 // System.out.println(table_name + "." + key.field_name + " is a primary key"); 00319 // System.out.println("\t > " + key.field_name); 00320 // System.out.println("\t > " + table.getField(key.field_name)); 00321 table.getField(key.field_name).isAforeignKey(reference_field); 00322 table.getField(key.field_name).isAHardForeignKey(); 00323 } 00324 00325 // System.out.println("FK setted"); 00326 00327 // Set all "nullable" fields. 00328 for (String nullable_field: this._null(this.__connection, this.__db_meta, table)) 00329 { 00330 // System.out.println(nullable_field); 00331 Field field = table.getField(nullable_field); 00332 if (null == field) { throw new Exception("Unexpected error : the database seems to be invalid. Can not find field (\"" + table_name + "." + nullable_field + "\") ?"); } 00333 field.isANull(); 00334 } 00335 } 00336 00337 // Find for soft foreign keys. 00338 AbstractSotfForeignKeyDetector fkDetector = this._getSoftForeignKeyDetector_(in_profile); 00339 00340 if (null != fkDetector) 00341 { 00342 for (String table_name: tables) 00343 { 00344 Table table = database.getTable(table_name); 00345 for (Field field: table.getFields()) 00346 { 00347 String field_name = field.getName(); 00348 if (! fkDetector.isFk(field_name)) { continue; } 00349 00350 // A hard foreign key may look like a soft one! 00351 if (field.isForeignKey()) { continue; } 00352 00353 String target_table_name = fkDetector.referenceTable(field_name); 00354 String target_field_name = fkDetector.referenceField(field_name); 00355 00356 // Get the target table. 00357 Table target_table = database.getTable(target_table_name); 00358 if (null == target_table) { throw new Exception("Unexpected error : the database seems to be invalid. Found a SOFT foreign key (\"" + field_name + "\") witch target's table (\"" + target_table_name + "\") does not exist!"); } 00359 00360 // Get the target field. 00361 Field target_field = target_table.getField(target_field_name); 00362 if (null == target_field) { throw new Exception("Unexpected error : the database seems to be invalid. Found a SOFT foreign key (\"" + field_name + "\") witch target's field (\"" + target_table_name + "." + target_field_name + "\") does not exist!"); } 00363 00364 // Set the field a soft foreign key. 00365 field.isAforeignKey(target_field); 00366 field.isASoftForeignKey(); 00367 } 00368 } 00369 } 00370 00371 // Now, generate the list of relations. 00372 database.generateRelations(); 00373 00374 00375 return database; 00376 } 00377 }