1 19 20 21 package org.apache.cayenne.access; 22 23 import java.sql.Connection ; 24 import java.sql.DatabaseMetaData ; 25 import java.sql.ResultSet ; 26 import java.sql.SQLException ; 27 import java.util.ArrayList ; 28 import java.util.Arrays ; 29 import java.util.Collection ; 30 import java.util.HashMap ; 31 import java.util.HashSet ; 32 import java.util.Iterator ; 33 import java.util.List ; 34 import java.util.Map ; 35 import java.util.Set ; 36 37 import org.apache.cayenne.CayenneException; 38 import org.apache.cayenne.dba.DbAdapter; 39 import org.apache.cayenne.dba.TypesMapping; 40 import org.apache.cayenne.map.DataMap; 41 import org.apache.cayenne.map.DbAttribute; 42 import org.apache.cayenne.map.DbEntity; 43 import org.apache.cayenne.map.DbJoin; 44 import org.apache.cayenne.map.DbRelationship; 45 import org.apache.cayenne.map.Entity; 46 import org.apache.cayenne.map.ObjEntity; 47 import org.apache.cayenne.map.Procedure; 48 import org.apache.cayenne.map.ProcedureParameter; 49 import org.apache.cayenne.util.EntityMergeSupport; 50 import org.apache.cayenne.util.NameConverter; 51 import org.apache.cayenne.util.Util; 52 import org.apache.commons.logging.Log; 53 import org.apache.commons.logging.LogFactory; 54 import org.objectstyle.ashwood.dbutil.Table; 55 56 63 public class DbLoader { 64 65 private static Log logObj = LogFactory.getLog(DbLoader.class); 66 67 private static final Collection EXCLUDED_PROCEDURES = Arrays.asList(new Object [] { 70 "auto_pk_for_table", "auto_pk_for_table;1" 74 }); 75 76 public static final String WILDCARD = "%"; 77 78 79 private List dbEntityList = new ArrayList (); 80 81 85 private Set skippedEntities = new HashSet (); 86 87 88 private static String defaultRelName(String dstName, boolean toMany) { 89 String uglyName = (toMany) ? dstName + "_ARRAY" : "to_" + dstName; 90 return NameConverter.underscoredToJava(uglyName, false); 91 } 92 93 94 private static String uniqueRelName(Entity entity, String dstName, boolean toMany) { 95 int currentSuffix = 1; 96 String baseRelName = defaultRelName(dstName, toMany); 97 String relName = baseRelName; 98 99 while (entity.getRelationship(relName) != null) { 100 relName = baseRelName + currentSuffix; 101 currentSuffix++; 102 } 103 return relName; 104 } 105 106 protected Connection con; 107 protected DbAdapter adapter; 108 protected DatabaseMetaData metaData; 109 protected DbLoaderDelegate delegate; 110 protected String genericClassName; 111 112 113 public DbLoader(Connection con, DbAdapter adapter, DbLoaderDelegate delegate) { 114 this.adapter = adapter; 115 this.con = con; 116 this.delegate = delegate; 117 } 118 119 122 public DatabaseMetaData getMetaData() throws SQLException { 123 if (null == metaData) 124 metaData = con.getMetaData(); 125 return metaData; 126 } 127 128 131 public Connection getCon() { 132 return con; 133 } 134 135 143 public String getGenericClassName() { 144 return genericClassName; 145 } 146 147 155 public void setGenericClassName(String genericClassName) { 156 this.genericClassName = genericClassName; 157 } 158 159 164 public DbAdapter getAdapter() { 165 return adapter; 166 } 167 168 173 public List getCatalogs() throws SQLException { 174 List catalogs = new ArrayList (); 175 ResultSet rs = getMetaData().getCatalogs(); 176 177 try { 178 while (rs.next()) { 179 String catalog_name = rs.getString(1); 180 catalogs.add(catalog_name); 181 } 182 } 183 finally { 184 rs.close(); 185 } 186 return catalogs; 187 } 188 189 194 public List getSchemas() throws SQLException { 195 List schemas = new ArrayList (); 196 ResultSet rs = getMetaData().getSchemas(); 197 198 try { 199 while (rs.next()) { 200 String schema_name = rs.getString(1); 201 schemas.add(schema_name); 202 } 203 } 204 finally { 205 rs.close(); 206 } 207 return schemas; 208 } 209 210 216 public List getTableTypes() throws SQLException { 217 List types = new ArrayList (); 218 ResultSet rs = getMetaData().getTableTypes(); 219 220 try { 221 while (rs.next()) { 222 types.add(rs.getString("TABLE_TYPE").trim()); 223 } 224 } 225 finally { 226 rs.close(); 227 } 228 return types; 229 } 230 231 241 public List getTables( 242 String catalog, 243 String schemaPattern, 244 String tableNamePattern, 245 String [] types) throws SQLException { 246 247 List tables = new ArrayList (); 248 249 if (logObj.isDebugEnabled()) { 250 logObj.debug("Read tables: catalog=" 251 + catalog 252 + ", schema=" 253 + schemaPattern 254 + ", tableNames=" 255 + tableNamePattern); 256 257 if (types != null && types.length > 0) { 258 for (int i = 0; i < types.length; i++) { 259 logObj.debug("Read tables: table type=" + types[i]); 260 } 261 } 262 } 263 264 ResultSet rs = getMetaData().getTables( 265 catalog, 266 schemaPattern, 267 tableNamePattern, 268 types); 269 270 try { 271 while (rs.next()) { 272 String cat = rs.getString("TABLE_CAT"); 273 String schema = rs.getString("TABLE_SCHEM"); 274 String name = rs.getString("TABLE_NAME"); 275 276 281 if (name == null || name.startsWith("BIN$")) { 284 continue; 285 } 286 287 Table info = new Table(cat, schema, name); 288 tables.add(info); 289 } 290 } 291 finally { 292 rs.close(); 293 } 294 return tables; 295 } 296 297 305 public boolean loadDbEntities(DataMap map, List tables) throws SQLException { 306 this.dbEntityList = new ArrayList (); 307 308 Iterator iter = tables.iterator(); 309 while (iter.hasNext()) { 310 Table table = (Table) iter.next(); 311 312 DbEntity oldEnt = map.getDbEntity(table.getName()); 315 if (oldEnt != null) { 316 if (delegate == null) { 317 return false; 319 } 320 321 try { 322 if (delegate.overwriteDbEntity(oldEnt)) { 323 logObj.debug("Overwrite: " + oldEnt.getName()); 324 map.removeDbEntity(oldEnt.getName(), true); 325 delegate.dbEntityRemoved(oldEnt); 326 } 327 else { 328 logObj.debug("Keep old: " + oldEnt.getName()); 329 330 skippedEntities.add(oldEnt); 333 continue; 334 } 335 } 336 catch (CayenneException ex) { 337 logObj.debug("Load canceled."); 338 339 return false; 341 } 342 } 343 344 DbEntity dbEntity = new DbEntity(); 345 dbEntity.setName(table.getName()); 346 dbEntity.setSchema(table.getSchema()); 347 dbEntity.setCatalog(table.getCatalog()); 348 349 ResultSet rs = getMetaData().getColumns( 351 table.getCatalog(), 352 table.getSchema(), 353 table.getName(), 354 "%"); 355 356 try { 357 while (rs.next()) { 358 364 String tableName = rs.getString("TABLE_NAME"); 365 if (!dbEntity.getName().equals(tableName)) { 366 logObj.info("Incorrectly returned columns for '" 367 + tableName 368 + ", skipping."); 369 continue; 370 } 371 372 String columnName = rs.getString("COLUMN_NAME"); 374 375 boolean allowNulls = rs.getBoolean("NULLABLE"); 376 int columnType = rs.getInt("DATA_TYPE"); 377 int columnSize = rs.getInt("COLUMN_SIZE"); 378 String typeName = rs.getString("TYPE_NAME"); 379 380 int decimalDigits = -1; 382 if (TypesMapping.isDecimal(columnType)) { 383 decimalDigits = rs.getInt("DECIMAL_DIGITS"); 384 if (rs.wasNull()) { 385 decimalDigits = -1; 386 } 387 } 388 389 DbAttribute attr = adapter.buildAttribute( 391 columnName, 392 typeName, 393 columnType, 394 columnSize, 395 decimalDigits, 396 allowNulls); 397 attr.setEntity(dbEntity); 398 dbEntity.addAttribute(attr); 399 } 400 } 401 finally { 402 rs.close(); 403 } 404 405 map.addDbEntity(dbEntity); 406 407 if (delegate != null) { 409 delegate.dbEntityAdded(dbEntity); 410 } 411 412 if (map.getDbEntity(table.getName()) == dbEntity) { 416 this.dbEntityList.add(dbEntity); 417 } 418 } 419 420 Iterator i = map.getDbEntities().iterator(); 422 while (i.hasNext()) { 423 DbEntity dbEntity = (DbEntity) i.next(); 424 String tableName = dbEntity.getName(); 425 ResultSet rs = metaData.getPrimaryKeys(null, dbEntity.getSchema(), tableName); 426 427 try { 428 while (rs.next()) { 429 String keyName = rs.getString(4); 430 DbAttribute attribute = (DbAttribute) dbEntity.getAttribute(keyName); 431 432 if (attribute != null) { 433 attribute.setPrimaryKey(true); 434 } 435 else { 436 logObj.warn("Can't locate attribute for primary key: " + keyName); 440 } 441 } 442 } 443 finally { 444 rs.close(); 445 } 446 } 447 448 Iterator skippedEntityIter = skippedEntities.iterator(); 450 while (skippedEntityIter.hasNext()) { 451 452 DbEntity skippedEntity = (DbEntity) skippedEntityIter.next(); 453 loadDbRelationships(skippedEntity, map); 454 } 455 456 return true; 457 458 } 459 460 464 public void loadObjEntities(DataMap map) { 465 466 Iterator dbEntities = dbEntityList.iterator(); 467 if (!dbEntities.hasNext()) { 468 return; 469 } 470 471 List loadedEntities = new ArrayList (dbEntityList.size()); 472 473 String packageName = map.getDefaultPackage(); 474 if (Util.isEmptyString(packageName)) { 475 packageName = ""; 476 } 477 else if (!packageName.endsWith(".")) { 478 packageName = packageName + "."; 479 } 480 481 while (dbEntities.hasNext()) { 483 DbEntity dbEntity = (DbEntity) dbEntities.next(); 484 485 Collection existing = map.getMappedEntities(dbEntity); 487 if (existing.size() > 0) { 488 loadedEntities.addAll(existing); 489 continue; 490 } 491 492 String objEntityName = NameConverter.underscoredToJava( 493 dbEntity.getName(), 494 true); 495 String baseName = objEntityName; 498 for (int i = 1; i < 1000 && map.getObjEntity(objEntityName) != null; i++) { 499 objEntityName = baseName + i; 500 } 501 502 ObjEntity objEntity = new ObjEntity(objEntityName); 503 objEntity.setDbEntity(dbEntity); 504 505 objEntity.setClassName(getGenericClassName() != null 506 ? getGenericClassName() 507 : packageName + objEntity.getName()); 508 map.addObjEntity(objEntity); 509 loadedEntities.add(objEntity); 510 511 if (delegate != null) { 513 delegate.objEntityAdded(objEntity); 514 } 515 } 516 517 new EntityMergeSupport(map).synchronizeWithDbEntities(loadedEntities); 519 } 520 521 522 public void loadDbRelationships(DataMap map) throws SQLException { 523 Iterator it = dbEntityList.iterator(); 524 while (it.hasNext()) { 525 DbEntity pkEntity = (DbEntity) it.next(); 526 loadDbRelationships(pkEntity, map); 527 } 528 } 529 530 private void loadDbRelationships(DbEntity pkEntity, DataMap map) throws SQLException { 531 DatabaseMetaData md = getMetaData(); 532 String pkEntName = pkEntity.getName(); 533 534 ResultSet rs = null; 536 537 try { 538 rs = md.getExportedKeys( 539 pkEntity.getCatalog(), 540 pkEntity.getSchema(), 541 pkEntity.getName()); 542 } 543 catch (SQLException cay182Ex) { 544 logObj.info("Error getting relationships for '" 546 + pkEntName 547 + "', ignoring."); 548 return; 549 } 550 551 try { 552 if (!rs.next()) 553 return; 554 555 DbRelationship forwardRelationship = null; 559 DbRelationship reverseRelationship = null; 560 DbEntity fkEntity = null; 561 562 do { 563 short keySeq = rs.getShort("KEY_SEQ"); 564 if (keySeq == 1) { 565 566 if (forwardRelationship != null) { 567 postprocessMasterDbRelationship(forwardRelationship); 568 forwardRelationship = null; 569 } 570 571 String fkEntityName = rs.getString("FKTABLE_NAME"); 573 574 fkEntity = map.getDbEntity(fkEntityName); 575 576 if (fkEntity == null) { 577 logObj.info("FK warning: no entity found for name '" 578 + fkEntityName 579 + "'"); 580 } else if (skippedEntities.contains(pkEntity) && skippedEntities.contains(fkEntity)) { 581 continue; 584 } 585 else { 586 587 forwardRelationship = new DbRelationship(DbLoader 589 .uniqueRelName(pkEntity, fkEntityName, true)); 590 591 forwardRelationship.setSourceEntity(pkEntity); 592 forwardRelationship.setTargetEntity(fkEntity); 593 pkEntity.addRelationship(forwardRelationship); 594 595 reverseRelationship = new DbRelationship(uniqueRelName( 596 fkEntity, 597 pkEntName, 598 false)); 599 reverseRelationship.setToMany(false); 600 reverseRelationship.setSourceEntity(fkEntity); 601 reverseRelationship.setTargetEntity(pkEntity); 602 fkEntity.addRelationship(reverseRelationship); 603 } 604 } 605 606 if (fkEntity != null) { 607 String pkName = rs.getString("PKCOLUMN_NAME"); 609 String fkName = rs.getString("FKCOLUMN_NAME"); 610 611 DbAttribute pkAtt = (DbAttribute) pkEntity.getAttribute(pkName); 613 if (pkAtt == null) { 614 logObj.info("no attribute for declared primary key: " 615 + pkName); 616 continue; 617 } 618 619 DbAttribute fkAtt = (DbAttribute) fkEntity.getAttribute(fkName); 620 if (fkAtt == null) { 621 logObj.info("no attribute for declared foreign key: " 622 + fkName); 623 continue; 624 } 625 626 forwardRelationship.addJoin(new DbJoin( 627 forwardRelationship, 628 pkName, 629 fkName)); 630 reverseRelationship.addJoin(new DbJoin( 631 reverseRelationship, 632 fkName, 633 pkName)); 634 } 635 } while (rs.next()); 636 637 if (forwardRelationship != null) { 638 postprocessMasterDbRelationship(forwardRelationship); 639 forwardRelationship = null; 640 } 641 642 } 643 finally { 644 rs.close(); 645 } 646 } 647 651 protected void postprocessMasterDbRelationship(DbRelationship relationship) { 652 boolean toPK = true; 653 List joins = relationship.getJoins(); 654 655 Iterator joinsIt = joins.iterator(); 656 while (joinsIt.hasNext()) { 657 DbJoin join = (DbJoin) joinsIt.next(); 658 if (!join.getTarget().isPrimaryKey()) { 659 toPK = false; 660 break; 661 } 662 663 } 664 665 boolean toDependentPK = false; 666 boolean toMany = true; 667 668 if (toPK) { 669 toDependentPK = true; 670 if (((DbEntity) relationship.getTargetEntity()).getPrimaryKey().size() == joins 671 .size()) { 672 toMany = false; 673 } 674 } 675 676 if (!toMany) { 678 Entity source = relationship.getSourceEntity(); 679 source.removeRelationship(relationship.getName()); 680 relationship.setName(DbLoader.uniqueRelName(source, relationship 681 .getTargetEntityName(), false)); 682 source.addRelationship(relationship); 683 } 684 685 relationship.setToDependentPK(toDependentPK); 686 relationship.setToMany(toMany); 687 } 688 689 private String [] getDefaultTableTypes() { 690 String viewType = adapter.tableTypeForView(); 691 String tableType = adapter.tableTypeForTable(); 692 693 List list = new ArrayList (); 695 if (viewType != null) { 696 list.add(viewType); 697 } 698 if (tableType != null) { 699 list.add(tableType); 700 } 701 702 String [] types = new String [list.size()]; 703 list.toArray(types); 704 return types; 705 } 706 707 713 public DataMap loadDataMapFromDB( 714 String schemaName, 715 String tablePattern, 716 DataMap dataMap) throws SQLException { 717 718 String [] types = getDefaultTableTypes(); 719 if (types.length == 0) { 720 throw new SQLException ("No supported table types found."); 721 } 722 723 return loadDataMapFromDB(schemaName, tablePattern, types, dataMap); 724 } 725 726 730 public DataMap loadDataMapFromDB( 731 String schemaName, 732 String tablePattern, 733 String [] tableTypes, 734 DataMap dataMap) throws SQLException { 735 736 if (tablePattern == null) { 737 tablePattern = WILDCARD; 738 } 739 740 if (!loadDbEntities( 741 dataMap, 742 getTables(null, schemaName, tablePattern, tableTypes))) { 743 return dataMap; 744 } 745 746 loadDbRelationships(dataMap); 747 loadObjEntities(dataMap); 748 return dataMap; 749 } 750 751 762 public void loadProceduresFromDB( 763 String schemaPattern, 764 String namePattern, 765 DataMap dataMap) throws SQLException { 766 767 Map procedures = null; 768 769 ResultSet rs = getMetaData().getProcedures(null, schemaPattern, namePattern); 771 try { 772 while (rs.next()) { 773 String name = rs.getString("PROCEDURE_NAME"); 774 775 if (EXCLUDED_PROCEDURES.contains(name)) { 777 logObj.info("skipping Cayenne PK procedure: " + name); 778 continue; 779 } 780 781 String catalog = rs.getString("PROCEDURE_CAT"); 782 String schema = rs.getString("PROCEDURE_SCHEM"); 783 784 short type = rs.getShort("PROCEDURE_TYPE"); 785 786 Procedure procedure = new Procedure(name); 787 procedure.setCatalog(catalog); 788 procedure.setSchema(schema); 789 790 switch (type) { 791 case DatabaseMetaData.procedureNoResult: 792 case DatabaseMetaData.procedureResultUnknown: 793 procedure.setReturningValue(false); 794 break; 795 case DatabaseMetaData.procedureReturnsResult: 796 procedure.setReturningValue(true); 797 break; 798 } 799 800 if (procedures == null) { 801 procedures = new HashMap (); 802 } 803 804 procedures.put(procedure.getFullyQualifiedName(), procedure); 805 } 806 } 807 finally { 808 rs.close(); 809 } 810 811 if (procedures == null) { 813 return; 814 } 815 816 ResultSet columnsRS = getMetaData().getProcedureColumns( 818 null, 819 schemaPattern, 820 namePattern, 821 null); 822 try { 823 while (columnsRS.next()) { 824 825 String schema = columnsRS.getString("PROCEDURE_SCHEM"); 826 String name = columnsRS.getString("PROCEDURE_NAME"); 827 828 if (EXCLUDED_PROCEDURES.contains(name)) { 830 continue; 831 } 832 833 String columnName = columnsRS.getString("COLUMN_NAME"); 834 short type = columnsRS.getShort("COLUMN_TYPE"); 835 836 String key = (schema != null) ? schema + '.' + name : name; 837 838 if (type == DatabaseMetaData.procedureColumnResult) { 841 logObj.debug("skipping ResultSet column: " + key + "." + columnName); 842 } 843 844 Procedure procedure = (Procedure) procedures.get(key); 845 846 if (procedure == null) { 847 logObj.info("invalid procedure column, no procedure found: " 848 + key 849 + "." 850 + columnName); 851 continue; 852 } 853 854 ProcedureParameter column = new ProcedureParameter(columnName); 855 856 if (columnName == null) { 857 if (type == DatabaseMetaData.procedureColumnReturn) { 858 logObj.debug("null column name, assuming result column: " + key); 859 column.setName("_return_value"); 860 } 861 else { 862 logObj.info("invalid null column name, skipping column : " + key); 863 continue; 864 } 865 } 866 867 int columnType = columnsRS.getInt("DATA_TYPE"); 868 int columnSize = columnsRS.getInt("LENGTH"); 869 870 int decimalDigits = -1; 872 if (TypesMapping.isDecimal(columnType)) { 873 decimalDigits = columnsRS.getShort("SCALE"); 874 if (columnsRS.wasNull()) { 875 decimalDigits = -1; 876 } 877 } 878 879 switch (type) { 880 case DatabaseMetaData.procedureColumnIn: 881 column.setDirection(ProcedureParameter.IN_PARAMETER); 882 break; 883 case DatabaseMetaData.procedureColumnInOut: 884 column.setDirection(ProcedureParameter.IN_OUT_PARAMETER); 885 break; 886 case DatabaseMetaData.procedureColumnOut: 887 column.setDirection(ProcedureParameter.OUT_PARAMETER); 888 break; 889 case DatabaseMetaData.procedureColumnReturn: 890 procedure.setReturningValue(true); 891 break; 892 } 893 894 column.setMaxLength(columnSize); 895 column.setPrecision(decimalDigits); 896 column.setProcedure(procedure); 897 column.setType(columnType); 898 procedure.addCallParameter(column); 899 } 900 } 901 finally { 902 columnsRS.close(); 903 } 904 905 Iterator it = procedures.values().iterator(); 906 while (it.hasNext()) { 907 909 Procedure procedure = (Procedure) it.next(); 910 dataMap.addProcedure(procedure); 911 } 912 } 913 } 914 | Popular Tags |