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