View Javadoc
1 /* 2 * PROJECT : DAR Runtime and Tools 3 * COPYRIGHT : Copyright (C) 1999-2004 tim.stephenson@enableit.org 4 * LICENSE : GNU LESSER GENERAL PUBLIC LICENSE 5 * Version 2.1, February 1999 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 package org.enableit.db.darrt; 22 23 import java.io.File; 24 import java.io.FileWriter; 25 import java.io.InputStream; 26 import java.io.InputStreamReader; 27 import java.net.URL; 28 import java.util.ArrayList; 29 import java.util.Iterator; 30 import java.util.List; 31 32 import org.apache.log4j.Logger; 33 import org.enableit.db.beans.Column; 34 import org.enableit.db.beans.Database; 35 import org.enableit.db.beans.Provider; 36 import org.enableit.db.beans.ProviderExt; 37 import org.enableit.db.beans.Schema; 38 import org.enableit.db.beans.Table; 39 import org.enableit.db.beans.View; 40 import org.enableit.db.darrt.beans.DiffData; 41 import org.exolab.castor.xml.Marshaller; 42 import org.exolab.castor.xml.Unmarshaller; 43 import org.w3c.dom.Document; 44 45 46 /*** 47 * An abstract ancestor that takes care of many of the common operations 48 * required when handling the XML and JDBC metadata. 49 * 50 * @author default 51 */ 52 public abstract class AbstractSchemaHandler extends AbstractFileHandler { 53 /*** 54 * The Log4J <code>Logger</code> doing the logging. 55 */ 56 private static Logger logger = Logger.getLogger(AbstractSchemaHandler.class); 57 private ArrayList listeners = new ArrayList(); 58 private java.net.URL refSchemaUrl; 59 private java.net.URL targetSchemaUrl; 60 private Provider onlineRefSchema; 61 private Provider onlineTargetSchema; 62 private Database refDatabase; 63 private Database targetDatabase; 64 private boolean debug = true; 65 66 /*** 67 * The reference schema name. 68 */ 69 private String refSchemaName; 70 71 /*** 72 * The table pattern to operate on. 73 */ 74 private String tablePattern; 75 76 /*** 77 * @return The table pattern to operate on. 78 */ 79 public String getTablePattern() { 80 return this.tablePattern; 81 } 82 83 /*** 84 * @param tablePattern A pattern identifying tables to operate on. 85 * 86 * <p>Single chars may be represented by '_' and multiple chars by '%'.</p> 87 */ 88 public void setTablePattern(String tablePattern) { 89 logger.info("METHOD_ENTRY: setTablePattern, tablePattern=" 90 + tablePattern); 91 this.tablePattern = tablePattern; 92 logger.info("METHOD_EXIT: setTablePattern"); 93 } 94 95 /*** 96 * @return The name of the reference schema to operate on. 97 */ 98 public String getRefSchemaName() { 99 return this.refSchemaName; 100 } 101 102 /*** 103 * @param refSchemaName The name of the reference schema to operate on. 104 */ 105 public void setRefSchemaName(String refSchemaName) { 106 logger.info("METHOD_ENTRY: setRefSchema: " + refSchemaName); 107 108 this.refSchemaName = refSchemaName; 109 110 logger.info("METHOD_EXIT: setRefSchema"); 111 } 112 113 /*** 114 * Specify a JDBC URL to use as the target data source. 115 */ 116 public URL getTargetSchemaUrl() { 117 return targetSchemaUrl; 118 } 119 120 /*** 121 * @param newTargetSchemaUrl 122 */ 123 public void setTargetSchemaUrl(URL newTargetSchemaUrl) { 124 this.targetSchemaUrl = newTargetSchemaUrl; 125 } 126 127 /*** 128 * @param targetProvider A <code>Provider</code> representing connection 129 * details for the target online schema. 130 */ 131 public void setOnlineTargetSchema(Provider targetProvider) { 132 this.onlineTargetSchema = targetProvider; 133 } 134 135 /*** 136 * @return <code>Provider</code> representing connection details for the 137 * target online schema, or null if not set. 138 */ 139 public Provider getOnlineTargetSchema() { 140 return onlineTargetSchema; 141 } 142 143 /*** 144 * @param targetDatabase The object representation of the target schema. 145 */ 146 public void setTargetDatabase(Database targetDatabase) { 147 this.targetDatabase = targetDatabase; 148 } 149 150 /*** 151 * @return The object representation of the target schema. 152 */ 153 public Database getTargetDatabase() { 154 return targetDatabase; 155 } 156 157 /*** 158 * @return URL for reference schema, or null if not set. 159 */ 160 public URL getRefSchemaUrl() { 161 return refSchemaUrl; 162 } 163 164 /*** @param newSchemaUrl */ 165 public void setRefSchemaUrl(java.net.URL newSchemaUrl) { 166 this.refSchemaUrl = newSchemaUrl; 167 } 168 169 /*** 170 * @param refProvider A <code>Provider</code> representing connection 171 * details for the target online schema. 172 */ 173 public void setOnlineRefSchema(Provider refProvider) { 174 this.onlineRefSchema = refProvider; 175 } 176 177 /*** 178 * @return <code>Provider</code> representing connection details for the 179 * reference online schema, or null if not set. 180 */ 181 public Provider getOnlineRefSchema() { 182 return onlineRefSchema; 183 } 184 185 /*** 186 * @param Sets the object representation of the reference online schema. 187 */ 188 public void setRefDatabase(Database refDatabase) { 189 this.refDatabase = refDatabase; 190 } 191 192 /*** 193 * @return The object representation of the reference online schema 194 */ 195 public Database getRefDatabase() { 196 return refDatabase; 197 } 198 199 /*** 200 * Extracts a database schema from a JDBC URL by infering the 201 * database type and driver name. 202 * 203 * @param jdbcUrl The JDBC URL to extract database info for. 204 * @returns Database instance representing the schema. 205 * @throws SchemaHandlingException If the database url is of an unknown type 206 * (i.e. do not know the Driver to use) or if an error occurs during 207 * the export. 208 */ 209 protected Database getDatabase(Provider provider) 210 throws SchemaHandlingException { 211 logger.info("METHOD_ENTRY: getDatabase"); 212 213 Database database = null; 214 215 try { 216 logger.warn("Exporting schema from provider: " 217 + ProviderExt.toString(provider)); 218 219 SchemaExporter se = new SchemaExporter(); 220 221 se.setTablePattern("%"); 222 223 // Assume the target schema is the one for this login 224 // (there must be exactly one schema in the target, though 225 // multiples may be exported) 226 se.setSchemaName(provider.getUsername()); 227 228 if (provider.getSchemaName() != null) { 229 se.setSchemaName(provider.getSchemaName()); 230 } else if (SchemaConstants.PN_MS_SQL_SERVER.equals( 231 provider.getProductName())) { 232 String msg = "You must specify a schema name when using " 233 + SchemaConstants.PN_MS_SQL_SERVER; 234 logger.error(msg) ; 235 throw new SchemaHandlingException(msg) ; 236 } 237 238 String[] tableTypes = { "TABLE", "VIEW" }; 239 240 se.setTableTypes(tableTypes); // TODO Is this a constant? 241 se.setDebug(debug); 242 243 Document doc = se.export(provider); 244 245 // TODO if in debug mode write this out to file? 246 if (debug) { 247 try { 248 File target = new File("target-schema.xml"); 249 FileWriter fw = new FileWriter(target); 250 251 Marshaller.marshal(database, fw); 252 fw.close(); 253 } catch (Exception e) { 254 // Do not allow this to cause failure 255 logger.error("When writing debug file " 256 + "'target-schema.xml':"); 257 logger.error(e.getClass().getName() + ":" + e.getMessage(), 258 e); 259 } 260 } 261 262 //logger.debug("Check that there is only one schema returned: " +); 263 database = (Database) Unmarshaller.unmarshal(Database.class, doc); 264 } catch (SchemaHandlingException e) { 265 throw e ; // already logged 266 } catch (Exception e) { 267 throw new SchemaHandlingException(e.getClass().getName() + ":" 268 + e.getMessage(), e); 269 } 270 271 logger.info("METHOD_EXIT: getDatabase"); 272 273 return database; 274 } 275 276 /*** 277 * Parses a schema. 278 */ 279 private void parseSchemas() 280 throws SchemaHandlingException { 281 logger.info("METHOD_ENTRY: parseSchema"); 282 283 try { 284 /* 285 * It is possible that the schema may have been set directly 286 *(ie no need to parse) 287 */ 288 if (refDatabase == null) { 289 if (refSchemaUrl != null) { 290 logger.warn("Parsing reference schema from URL:" 291 + refSchemaUrl); 292 293 InputStream input = refSchemaUrl.openStream(); 294 295 refDatabase = Database.unmarshal(new InputStreamReader( 296 input)); 297 input.close(); 298 299 //logger.debug("No of tables=" + refDatabase.getSchema().getTableCount()) ; 300 //logger.debug("Tables:" + Arrays.asList(refDatabase.getSchema().getTable())) ; 301 } else { 302 // get the schema from online database 303 logger.warn("Parsing reference schema from database:" 304 + onlineRefSchema.getUrl()); 305 refDatabase = getDatabase(onlineRefSchema); 306 } 307 } 308 309 /* 310 * It is possible that the schema may have been set directly 311 * (ie no need to parse) 312 */ 313 if (targetDatabase == null) { 314 if (targetSchemaUrl != null) { 315 logger.warn("Parsing target schema from URL:" 316 + targetSchemaUrl); 317 318 InputStream input = targetSchemaUrl.openStream(); 319 320 targetDatabase = Database.unmarshal(new InputStreamReader( 321 input)); 322 input.close(); 323 324 //logger.debug("No of tables=" + targetDatabase.getSchema().getTableCount()) ; 325 //logger.debug("Tables:" + Arrays.asList(targetDatabase.getSchema().getTable())) ; 326 } else { 327 // get the schema from online database 328 logger.warn("Parsing target schema from database:" 329 + onlineTargetSchema.getUrl()); 330 targetDatabase = getDatabase(onlineTargetSchema); 331 } 332 } 333 } catch (java.io.IOException e) { 334 // TODO attempt to continue 335 logger.fatal(e); 336 throw new SchemaHandlingException(e.getMessage()); 337 } catch (org.exolab.castor.xml.MarshalException e) { 338 // TODO attempt to continue 339 logger.fatal(e); 340 e.printStackTrace(); 341 throw new SchemaHandlingException(e.getMessage()); 342 } catch (org.exolab.castor.xml.ValidationException e) { 343 // TODO attempt to continue 344 logger.fatal(e); 345 e.printStackTrace(); 346 throw new SchemaHandlingException(e.getMessage()); 347 } 348 349 // Must ALWAYS have two schemas by this point 350 if (refDatabase == null) { 351 throw new SchemaHandlingException("Reference schema is missing."); 352 } 353 354 if (targetDatabase == null) { 355 throw new SchemaHandlingException("Target schema is missing."); 356 } 357 358 logger.info("METHOD_EXIT: parseSchema"); 359 } 360 361 /*** 362 * Returns the differences between the reference and target schemas 363 * as a <code>List</code> of <code>DiffData</code> objects. 364 */ 365 public List diffSchemas() 366 throws SchemaHandlingException { 367 List diffList = new ArrayList(); 368 369 checkMandatory(); 370 parseSchemas(); 371 372 raiseEvent(DarrtEvent.START_SCHEMA_DIFF, 373 refDatabase.getProvider().getUrl() + ", " 374 + targetDatabase.getProvider().getUrl()); 375 376 // Identify schemas within ref and target databases 377 Schema refSchema = null ; 378 for (int s = 0; s < refDatabase.getSchemaCount(); s++) { 379 refSchema = refDatabase.getSchema(s) ; 380 if (refSchema.getName().equalsIgnoreCase( 381 refDatabase.getProvider().getSchemaName())) { 382 refSchema = refDatabase.getSchema(s); 383 break ; 384 } 385 } 386 387 Schema targetSchema = null ; 388 for (int s = 0; s < targetDatabase.getSchemaCount(); s++) { 389 targetSchema = targetDatabase.getSchema(s) ; 390 if (targetSchema.getName().equalsIgnoreCase( 391 targetDatabase.getProvider().getSchemaName())) { 392 targetSchema = targetDatabase.getSchema(s); 393 break ; 394 } 395 } 396 397 // Iterate reference tables 398 /* for (int s = 0; s < refDatabase.getSchemaCount(); s++) { 399 Schema refSchema = refDatabase.getSchema(s); 400 Schema targetSchema = null; 401 402 for (int t = 0; t < targetDatabase.getSchemaCount(); t++) { 403 if (refSchema.getName().equals(targetDatabase.getSchema(t) 404 .getName())) { 405 targetSchema = targetDatabase.getSchema(t); 406 407 break; 408 } 409 } 410 */ 411 int refSize = (refSchema == null) ? 0 : refSchema.getTableCount(); 412 int targetSize = (targetSchema == null) ? 0 413 : targetSchema 414 .getTableCount(); 415 416 if (refSize == 0) { 417 logger.warn("No tables found in the reference schema " 418 + (refSchema == null ? "unknown" : refSchema.getName())); 419 } 420 421 if (targetSize == 0) { 422 logger.warn("No tables found in the target schema " 423 + (targetSchema == null ? "unknown" : targetSchema.getName())); 424 } 425 426 boolean foundTable = false; 427 428 for (int i = 0; i < refSize; i++) { 429 // Search for matching table in target 430 Table rTable = (Table) refSchema.getTable(i); 431 432 for (int j = 0; j < targetSize; j++) { 433 Table tTable = (Table) targetSchema.getTable(j); 434 435 if (rTable.getName().equals(tTable.getName())) { 436 logger.debug("Matched table named " + tTable.getName()); 437 438 if (rTable.equals(tTable)) { 439 // No need to do column wise comparison 440 logger.debug("Tables are exact match: " 441 + rTable.hashCode() + "," + tTable.hashCode()); 442 } else { 443 /* 444 * Diff the columns 445 */ 446 for (int k = 0; k < rTable.getColumn().length; 447 k++) { 448 boolean foundColumn = false; 449 String rName = rTable.getColumn(k).getColName(); 450 451 for (int l = 0; l < tTable.getColumn().length; 452 l++) { 453 if (rName.equals(tTable.getColumn(l) 454 .getColName())) { 455 logger.debug("Matched column named " 456 + tTable.getName() + "." 457 + tTable.getColumn(l).getColName()); 458 459 String rPK = ((rTable.getColumn(k) 460 .getPrimaryKey() == null) 461 ? "false" 462 : rTable.getColumn(k).getPrimaryKey()); 463 String tPK = ((tTable.getColumn(l) 464 .getPrimaryKey() == null) 465 ? "false" 466 : tTable.getColumn(l).getPrimaryKey()); 467 468 if (rTable.getColumn(k).getColType() 469 .equals(tTable.getColumn( 470 l).getColType()) 471 && rTable.getColumn(k) 472 .getNull() 473 .equals(tTable.getColumn( 474 l).getNull()) 475 && rPK.equals(tPK)) { 476 // exact match (TODO match FK) 477 logger.debug( 478 "Columns are exact match: " 479 + rTable.getName() + "." 480 + rName); 481 } else { 482 DiffData diffData = new DiffData(rTable 483 .getName() + "." + rName, 484 "Column", 485 rTable.getColumn(k), 486 tTable.getColumn(l)); 487 488 diffList.add(diffData); 489 raiseEvent(DarrtEvent.DIFFERENCE, 490 diffData); 491 } 492 493 foundColumn = true; 494 495 break; 496 } 497 } 498 499 if (!foundColumn) { 500 Column col = rTable.getColumn(k); 501 502 col.setTableName(rTable.getName()); 503 504 DiffData diffData = new DiffData(rTable 505 .getName() + "." + rName, "Column", 506 col, null); 507 508 diffList.add(diffData); 509 raiseEvent(DarrtEvent.DIFFERENCE, diffData); 510 } 511 512 foundColumn = false; 513 } 514 515 /* 516 * Diff the indexes 517 */ 518 for (int k = 0; k < rTable.getIndexCount(); k++) { 519 boolean foundIndex = false; 520 String rName = rTable.getIndex(k).getName(); 521 522 for (int l = 0; l < tTable.getIndexCount(); 523 l++) { 524 if (rTable.getIndex(k).getName().equals(tTable.getIndex( 525 l).getName()) 526 && rTable.getIndex(k).getUnique() 527 .equals(tTable.getIndex( 528 l).getUnique()) 529 && rTable.getIndex(k) 530 .getColName() 531 .equals(tTable.getIndex( 532 l).getColName()) 533 && rTable.getIndex(k).getType() 534 .equals(tTable.getIndex( 535 l).getType())) { 536 // exact match 537 logger.debug( 538 "Indexes are exact match: " 539 + rTable.getName() + "." + rName); 540 } else { 541 // The diff formatter needs to know the table index is on 542 rTable.getIndex(k).setTableName(rTable 543 .getName()); 544 tTable.getIndex(l).setTableName(tTable 545 .getName()); 546 logger.debug("**" 547 + tTable.getIndex(l).getTableName()); 548 549 DiffData diffData = new DiffData(rTable 550 .getName() + "." + rName, 551 "Index", rTable.getIndex(k), 552 tTable.getIndex(l)); 553 554 diffList.add(diffData); 555 raiseEvent(DarrtEvent.DIFFERENCE, 556 diffData); 557 } 558 } 559 } 560 } 561 562 foundTable = true; 563 564 break; 565 } 566 } 567 568 if (!foundTable) { 569 for (int k = 0; k < rTable.getIndexCount(); k++) { 570 // The diff formatter needs to know the table index is on 571 rTable.getIndex(k).setTableName(rTable.getName()); 572 } 573 574 DiffData diffData = new DiffData(rTable.getName(), "Table", 575 rTable, null); 576 577 diffList.add(diffData); 578 raiseEvent(DarrtEvent.DIFFERENCE, diffData); 579 } 580 581 foundTable = false; 582 } 583 584 // Iterate reference views 585 refSize = (refSchema == null) ? 0 : refSchema.getViewCount(); 586 targetSize = (targetSchema == null) ? 0 : targetSchema.getViewCount(); 587 588 if (refSize == 0) { 589 logger.warn("No views found in the reference schema"); 590 } 591 592 if (targetSize == 0) { 593 logger.warn("No views found in the reference schema"); 594 } 595 596 boolean foundView = false; 597 598 for (int i = 0; i < refSize; i++) { 599 // Search for matching view in target 600 View rView = (View) refSchema.getView(i); 601 602 for (int j = 0; j < targetSize; j++) { 603 View tView = (View) targetSchema.getView(j); 604 605 if (rView.getName().equals(tView.getName())) { 606 logger.debug("Matched view named " + tView.getName()); 607 608 if (rView.equals(tView)) { 609 // No need to do column wise comparison 610 logger.debug("Views are exact match: " 611 + rView.hashCode() + "," + tView.hashCode()); 612 } else { 613 /* 614 * Diff the columns 615 */ 616 for (int k = 0; k < rView.getColumn().length; 617 k++) { 618 boolean foundColumn = false; 619 String rName = rView.getColumn(k).getColName(); 620 621 for (int l = 0; l < tView.getColumn().length; 622 l++) { 623 if (rName.equals(tView.getColumn(l) 624 .getColName())) { 625 logger.debug("Matched column named " 626 + tView.getName() + "." 627 + tView.getColumn(l).getColName()); 628 629 String rPK = ((rView.getColumn(k) 630 .getPrimaryKey() == null) 631 ? "false" 632 : rView.getColumn(k).getPrimaryKey()); 633 String tPK = ((tView.getColumn(l) 634 .getPrimaryKey() == null) 635 ? "false" 636 : tView.getColumn(l).getPrimaryKey()); 637 638 if (rView.getColumn(k).getColType() 639 .equals(tView.getColumn( 640 l).getColType()) 641 && rView.getColumn(k) 642 .getNull() 643 .equals(tView.getColumn( 644 l).getNull()) 645 && rPK.equals(tPK)) { 646 // exact match (TODO match FK) 647 logger.debug( 648 "Columns are exact match: " 649 + rView.getName() + "." + rName); 650 } else { 651 DiffData diffData = new DiffData(rView 652 .getName() + "." + rName, 653 "Column", 654 rView.getColumn(k), 655 tView.getColumn(l)); 656 657 diffList.add(diffData); 658 raiseEvent(DarrtEvent.DIFFERENCE, 659 diffData); 660 } 661 662 foundColumn = true; 663 664 break; 665 } 666 } 667 668 if (!foundColumn) { 669 Column col = rView.getColumn(k); 670 671 col.setTableName(rView.getName()); 672 673 DiffData diffData = new DiffData(rView 674 .getName() + "." + rName, "Column", 675 col, null); 676 677 diffList.add(diffData); 678 raiseEvent(DarrtEvent.DIFFERENCE, diffData); 679 } 680 681 foundColumn = false; 682 } 683 684 /* 685 * Diff the definitions 686 */ 687 if (rView.getDefinition().equals(tView 688 .getDefinition())) { 689 // exact match 690 logger.debug("Definitions are exact match: " 691 + rView.getName()); 692 } else { 693 DiffData diffData = new DiffData(rView.getName(), 694 "View", rView.getDefinition(), 695 tView.getDefinition()); 696 697 diffList.add(diffData); 698 raiseEvent(DarrtEvent.DIFFERENCE, diffData); 699 } 700 } 701 702 foundView = true; 703 704 break; 705 } 706 } 707 708 if (!foundView) { 709 DiffData diffData = new DiffData(rView.getName(), "View", 710 rView, null); 711 712 diffList.add(diffData); 713 raiseEvent(DarrtEvent.DIFFERENCE, diffData); 714 } 715 716 foundView = false; 717 } 718 //} 719 720 raiseEvent(DarrtEvent.END_SCHEMA_DIFF, 721 refDatabase.getProvider().getUrl() + ", " 722 + targetDatabase.getProvider().getUrl()); 723 724 return diffList; 725 } 726 727 /*** 728 * Checks the mandatory properties have been set, so can continue. 729 */ 730 private void checkMandatory() 731 throws SchemaHandlingException { 732 // Verify have sufficient input 733 if ((refSchemaUrl == null) && (onlineRefSchema == null) 734 && (refDatabase == null)) { 735 throw new SchemaHandlingException( 736 "You must provide a reference schema."); 737 } 738 739 if ((targetSchemaUrl == null) && (onlineTargetSchema == null) 740 && (targetDatabase == null)) { 741 throw new SchemaHandlingException( 742 "You must provide a target schema."); 743 } 744 } 745 746 /*** 747 * Add a class interested in receiving events from the AbstractSchemaHandler. 748 * 749 * @param listener 750 */ 751 public void addListener(DarrtListener listener) { 752 listeners.add(listener); 753 } 754 755 /*** 756 * Add one or more classes interested in receiving events from the AbstractSchemaHandler. 757 * 758 * @param listeners List of DarrtListener instances. 759 */ 760 public void addListeners(List listeners) { 761 this.listeners.addAll(listeners); 762 } 763 764 /*** 765 * Notify listeners of the specified event. 766 */ 767 private void raiseEvent(int eventId, Object eventBean) { 768 DarrtEvent event = new DarrtEvent(eventId, eventBean); 769 770 for (Iterator i = listeners.iterator(); i.hasNext();) { 771 DarrtListener listener = (DarrtListener) i.next(); 772 773 listener.handle(event); 774 } 775 } 776 777 /*** 778 * @return <code>List</code> of registered listeners. 779 */ 780 public List getListeners() { 781 return listeners; 782 } 783 784 /*** 785 * @return Whether debugging is enabled. 786 */ 787 public boolean getDebug() { 788 return this.debug; 789 } 790 791 /*** 792 * @param New debug flag value. 793 */ 794 public void setDebug(boolean debug) { 795 this.debug = debug; 796 } 797 }

This page was automatically generated by Maven