001package org.andromda.schema2xmi; 002 003import java.sql.Connection; 004import java.sql.DatabaseMetaData; 005import java.sql.DriverManager; 006import java.sql.ResultSet; 007import java.sql.ResultSetMetaData; 008import java.sql.SQLException; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.Map; 014import java.util.Set; 015import java.util.StringTokenizer; 016import org.andromda.core.common.ExceptionUtils; 017import org.andromda.core.engine.ModelProcessorException; 018import org.andromda.core.mapping.Mappings; 019import org.andromda.core.namespace.NamespaceComponents; 020import org.andromda.core.repository.Repositories; 021import org.andromda.core.repository.RepositoryFacade; 022import org.apache.commons.dbutils.DbUtils; 023import org.apache.commons.lang.StringUtils; 024import org.apache.log4j.Logger; 025import org.omg.uml.UmlPackage; 026import org.omg.uml.foundation.core.AssociationEnd; 027import org.omg.uml.foundation.core.Attribute; 028import org.omg.uml.foundation.core.Classifier; 029import org.omg.uml.foundation.core.CorePackage; 030import org.omg.uml.foundation.core.DataType; 031import org.omg.uml.foundation.core.Stereotype; 032import org.omg.uml.foundation.core.TagDefinition; 033import org.omg.uml.foundation.core.TaggedValue; 034import org.omg.uml.foundation.core.UmlAssociation; 035import org.omg.uml.foundation.core.UmlClass; 036import org.omg.uml.foundation.datatypes.AggregationKindEnum; 037import org.omg.uml.foundation.datatypes.ChangeableKindEnum; 038import org.omg.uml.foundation.datatypes.DataTypesPackage; 039import org.omg.uml.foundation.datatypes.Multiplicity; 040import org.omg.uml.foundation.datatypes.MultiplicityRange; 041import org.omg.uml.foundation.datatypes.OrderingKindEnum; 042import org.omg.uml.foundation.datatypes.ScopeKindEnum; 043import org.omg.uml.foundation.datatypes.VisibilityKindEnum; 044import org.omg.uml.modelmanagement.Model; 045import org.omg.uml.modelmanagement.ModelManagementPackage; 046 047/** 048 * Performs the transformation of database schema to XMI. 049 * 050 * TODO This class really should have the functionality it uses (writing model 051 * elements) moved to the metafacades. 052 * TODO This class should be refactored into smaller classes. 053 * @author Chad Brandon 054 * @author Joel Kozikowski 055 */ 056public class SchemaTransformer 057{ 058 private static final Logger logger = Logger.getLogger(SchemaTransformer.class); 059 private RepositoryFacade repository = null; 060 061 /** 062 * The JDBC driver class 063 */ 064 private String jdbcDriver = null; 065 066 /** 067 * The JDBC schema user. 068 */ 069 private String jdbcUser = null; 070 071 /** 072 * The JDBC schema password. 073 */ 074 private String jdbcPassword = null; 075 076 /** 077 * The JDBC connection URL. 078 */ 079 private String jdbcConnectionUrl = null; 080 081 /** 082 * The name of the package in which the name of the elements will be 083 * created. 084 */ 085 private String packageName = null; 086 087 /** 088 * Stores the name of the schema where the tables can be found. 089 */ 090 private String schema = null; 091 092 /** 093 * The regular expression pattern to match on when deciding what table names 094 * to add to the transformed XMI. 095 */ 096 private String tableNamePattern = null; 097 098 /** 099 * Stores the schema types to model type mappings. 100 */ 101 private Mappings typeMappings = null; 102 103 /** 104 * Stores the classes keyed by table name. 105 */ 106 private Map<String, UmlClass> classes = new HashMap<String, UmlClass>(); 107 108 /** 109 * Stores the foreign keys for each table. 110 */ 111 private Map foreignKeys = new HashMap(); 112 113 /** 114 * Specifies the Class stereotype. 115 */ 116 private String classStereotypes = null; 117 118 /** 119 * Stores the name of the column tagged value to use for storing the name of 120 * the column. 121 */ 122 private String columnTaggedValue = null; 123 124 /** 125 * Stores the name of the table tagged value to use for storing the name of 126 * the table. 127 */ 128 private String tableTaggedValue = null; 129 130 /** 131 * The metadata column name needed to retrieve the database column type field. 132 */ 133 private String metaColumnTypeName = "TYPE_NAME"; 134 135 /** 136 * The metadata column name needed to retrieve the database column size field. 137 */ 138 private String metaColumnColumnSize = "COLUMN_SIZE"; 139// private String metaColumnColumnSize = "PRECISION"; 140 141 /** 142 * The metadata column name needed to retrieve the database column number of decimal places 143 */ 144 private String metaColumnDecPlaces = null; 145// private String metaColumnDecPlaces = "SCALE"; 146 147 /** 148 * The set of additional tagged values that are (optionally) added to each attribute 149 * in the generated model. 150 */ 151 private Map<String, String> attributeTaggedValues = new HashMap<String, String>(); 152 153 /** 154 * Stores the version of XMI that will be produced. 155 */ 156 private String xmiVersion = null; 157 158 /** 159 * Constructs a new instance of this SchemaTransformer. 160 * @param jdbcDriver 161 * @param jdbcConnectionUrl 162 * @param jdbcUser 163 * @param jdbcPassword 164 */ 165 public SchemaTransformer( 166 String jdbcDriver, 167 String jdbcConnectionUrl, 168 String jdbcUser, 169 String jdbcPassword) 170 { 171 ExceptionUtils.checkEmpty("jdbcDriver", jdbcDriver); 172 ExceptionUtils.checkEmpty("jdbcConnectionUrl", jdbcConnectionUrl); 173 ExceptionUtils.checkEmpty("jdbcUser", jdbcUser); 174 ExceptionUtils.checkEmpty("jdbcPassword", jdbcPassword); 175 176 NamespaceComponents.instance().discover(); 177 Repositories.instance().initialize(); 178 this.repository = Repositories.instance().getImplementation( 179 Schema2XMIGlobals.REPOSITORY_NAMESPACE_NETBEANSMDR); 180 if (repository == null) 181 { 182 throw new ModelProcessorException( 183 "No Repository could be found, please make sure you have a repository with namespace " 184 + Schema2XMIGlobals.REPOSITORY_NAMESPACE_NETBEANSMDR + 185 " on your classpath"); 186 } 187 this.repository.open(); 188 189 this.jdbcDriver = jdbcDriver; 190 this.jdbcConnectionUrl = jdbcConnectionUrl; 191 this.jdbcUser = jdbcUser; 192 this.jdbcPassword = jdbcPassword; 193 this.jdbcConnectionUrl = jdbcConnectionUrl; 194 } 195 196 /** 197 * Transforms the Schema file and writes it to the location given by 198 * <code>outputLocation</code>. The <code>inputModel</code> must be a 199 * valid URL, otherwise an exception will be thrown. 200 * 201 * @param inputModel the location of the input model to start with (if there 202 * is one) 203 * @param outputLocation The location to where the transformed output will 204 * be written. 205 */ 206 public void transform( 207 String inputModel, 208 String outputLocation) 209 { 210 long startTime = System.currentTimeMillis(); 211 outputLocation = StringUtils.trimToEmpty(outputLocation); 212 if (outputLocation == null) 213 { 214 throw new IllegalArgumentException("'outputLocation' can not be null"); 215 } 216 Connection connection = null; 217 try 218 { 219 if (inputModel != null) 220 { 221 logger.info("Input model --> '" + inputModel + '\''); 222 } 223 this.repository.readModel( 224 new String[] {inputModel}, 225 null); 226 Class.forName(this.jdbcDriver); 227 connection = DriverManager.getConnection(this.jdbcConnectionUrl, 228 this.jdbcUser, this.jdbcPassword); 229 repository.writeModel( 230 transform(connection), 231 outputLocation, 232 this.xmiVersion); 233 } 234 catch (Throwable th) 235 { 236 throw new SchemaTransformerException(th); 237 } 238 finally 239 { 240 DbUtils.closeQuietly(connection); 241 repository.close(); 242 } 243 logger.info( 244 "Completed adding " + this.classes.size() + " classes, writing model to --> '" + outputLocation + 245 "', TIME --> " + ((System.currentTimeMillis() - startTime) / 1000.0) + "[s]"); 246 } 247 248 /** 249 * Sets the <code>mappingsUri</code> which is the URI to the sql types to 250 * model type mappings. 251 * 252 * @param typeMappingsUri The typeMappings to set. 253 */ 254 public void setTypeMappings(String typeMappingsUri) 255 { 256 try 257 { 258 this.typeMappings = Mappings.getInstance(typeMappingsUri); 259 } 260 catch (final Throwable throwable) 261 { 262 throw new SchemaTransformerException(throwable); 263 } 264 } 265 266 /** 267 * Sets the name of the package to which the model elements will be created. 268 * 269 * @param packageName The packageName to set. 270 */ 271 public void setPackageName(String packageName) 272 { 273 this.packageName = packageName; 274 } 275 276 /** 277 * Sets the name of the schema (where the tables can be found). 278 * 279 * @param schema The schema to set. 280 */ 281 public void setSchema(String schema) 282 { 283 this.schema = schema; 284 } 285 286 /** 287 * Sets the regular expression pattern to match on when deciding what table 288 * names to add to the transformed XMI. 289 * 290 * @param tableNamePattern The tableNamePattern to set. 291 */ 292 public void setTableNamePattern(String tableNamePattern) 293 { 294 this.tableNamePattern = StringUtils.trimToEmpty(tableNamePattern); 295 } 296 297 /** 298 * The column name pattern. 299 */ 300 private String columnNamePattern; 301 302 /** 303 * Sets the regular expression pattern to match on when deciding what attributes 304 * to create in the XMI. 305 * 306 * @param columnNamePattern The pattern for filtering the column name. 307 */ 308 public void setColumnNamePattern(String columnNamePattern) 309 { 310 this.columnNamePattern = columnNamePattern; 311 } 312 313 /** 314 * Sets the stereotype name for the new classes. 315 * 316 * @param classStereotypes The classStereotypes to set. 317 */ 318 public void setClassStereotypes(String classStereotypes) 319 { 320 this.classStereotypes = StringUtils.deleteWhitespace(classStereotypes); 321 } 322 323 /** 324 * Specifies the identifier stereotype. 325 */ 326 private String identifierStereotypes = null; 327 328 /** 329 * Sets the stereotype name for the identifiers on the new classes. 330 * 331 * @param identifierStereotypes The identifierStereotypes to set. 332 */ 333 public void setIdentifierStereotypes(String identifierStereotypes) 334 { 335 this.identifierStereotypes = StringUtils.deleteWhitespace(identifierStereotypes); 336 } 337 338 /** 339 * Sets the name of the column tagged value to use for storing the name of 340 * the column. 341 * 342 * @param columnTaggedValue The columnTaggedValue to set. 343 */ 344 public void setColumnTaggedValue(String columnTaggedValue) 345 { 346 this.columnTaggedValue = StringUtils.trimToEmpty(columnTaggedValue); 347 } 348 349 /** 350 * Sets the name of the table tagged value to use for storing the name of 351 * the table. 352 * 353 * @param tableTaggedValue The tableTaggedValue to set. 354 */ 355 public void setTableTaggedValue(String tableTaggedValue) 356 { 357 this.tableTaggedValue = StringUtils.trimToEmpty(tableTaggedValue); 358 } 359 360 /** 361 * @param taggedValues 362 */ 363 public void setAttributeTaggedValues(String taggedValues) { 364 if (StringUtils.isNotBlank(taggedValues)) { 365 StringTokenizer tokList = new StringTokenizer(taggedValues, ","); 366 while (tokList.hasMoreTokens()) { 367 String tok = tokList.nextToken(); 368 String[] parts = StringUtils.split(tok, "="); 369 if (parts.length == 2) { 370 String tag = parts[0]; 371 String value = parts[1]; 372 attributeTaggedValues.put(tag, value); 373 } 374 } // while 375 } 376 } 377 378 /** 379 * Sets the version of XMI that will be produced. 380 * 381 * @param xmiVersion The xmiVersion to set. 382 */ 383 public void setXmiVersion(String xmiVersion) 384 { 385 this.xmiVersion = xmiVersion; 386 } 387 388 /** 389 * The package that is currently being processed. 390 */ 391 private UmlPackage umlPackage; 392 393 /** 394 * The model thats currently being processed 395 */ 396 private Model model; 397 398 /** 399 * Performs the actual translation of the Schema to the XMI and returns the 400 * object model. 401 */ 402 private final Object transform(final Connection connection) 403 throws Exception 404 { 405 this.umlPackage = (UmlPackage)this.repository.getModel().getModel(); 406 407 final ModelManagementPackage modelManagementPackage = umlPackage.getModelManagement(); 408 409 final Collection models = modelManagementPackage.getModel().refAllOfType(); 410 if (models != null && !models.isEmpty()) 411 { 412 // A given XMI file can contain multiple models. 413 // Use the first model in the XMI file 414 this.model = (Model)models.iterator().next(); 415 } 416 else 417 { 418 this.model = modelManagementPackage.getModel().createModel(); 419 } 420 421 // create the package on the model 422 org.omg.uml.modelmanagement.UmlPackage leafPackage = 423 this.getOrCreatePackage( 424 umlPackage.getModelManagement(), 425 model, 426 this.packageName); 427 this.createClasses( 428 connection, 429 umlPackage.getModelManagement().getCore(), 430 leafPackage); 431 432 return umlPackage; 433 } 434 435 /** 436 * Gets or creates a package having the specified <code>packageName</code> 437 * using the given <code>modelManagementPackage</code>, places it on the 438 * <code>model</code> and returns the last leaf package. 439 * 440 * @param modelManagementPackage from which we retrieve the UmlPackageClass 441 * to create a UmlPackage. 442 * @param modelPackage the root UmlPackage 443 * @param packageName 444 * @return modelPackage 445 */ 446 protected org.omg.uml.modelmanagement.UmlPackage getOrCreatePackage( 447 ModelManagementPackage modelManagementPackage, 448 org.omg.uml.modelmanagement.UmlPackage modelPackage, 449 String packageName) 450 { 451 packageName = StringUtils.trimToEmpty(packageName); 452 if (StringUtils.isNotBlank(packageName)) 453 { 454 String[] packages = packageName.split(Schema2XMIGlobals.PACKAGE_SEPARATOR); 455 if (packages != null && packages.length > 0) 456 { 457 for (int ctr = 0; ctr < packages.length; ctr++) 458 { 459 Object umlPackage = ModelElementFinder.find(modelPackage, packages[ctr]); 460 461 if (umlPackage == null) 462 { 463 umlPackage = 464 modelManagementPackage.getUmlPackage().createUmlPackage( 465 packages[ctr], VisibilityKindEnum.VK_PUBLIC, false, false, false, false); 466 modelPackage.getOwnedElement().add(umlPackage); 467 } 468 modelPackage = (org.omg.uml.modelmanagement.UmlPackage)umlPackage; 469 } 470 } 471 } 472 return modelPackage; 473 } 474 475 /** 476 * Creates all classes from the tables found in the schema. 477 * 478 * @param connection the Connection used to retrieve the schema metadata. 479 * @param corePackage the CorePackage instance we use to create the classes. 480 * @param modelPackage the package which the classes are added. 481 * @throws SQLException 482 */ 483 protected void createClasses( 484 Connection connection, 485 CorePackage corePackage, 486 org.omg.uml.modelmanagement.UmlPackage modelPackage) 487 throws SQLException 488 { 489 DatabaseMetaData metadata = connection.getMetaData(); 490 ResultSet tableRs = metadata.getTables( 491 null, 492 this.schema, 493 null, 494 new String[] {"TABLE"}); 495 496 // loop through and create all classes and store then 497 // in the classes Map keyed by table 498 while (tableRs.next()) 499 { 500 String tableName = tableRs.getString("TABLE_NAME"); 501 if (StringUtils.isNotBlank(this.tableNamePattern)) 502 { 503 if (tableName.matches(this.tableNamePattern)) 504 { 505 UmlClass umlClass = this.createClass(modelPackage, metadata, corePackage, tableName); 506 this.classes.put(tableName, umlClass); 507 } 508 } 509 else 510 { 511 UmlClass umlClass = this.createClass(modelPackage, metadata, corePackage, tableName); 512 this.classes.put(tableName, umlClass); 513 } 514 } 515 DbUtils.closeQuietly(tableRs); 516 if (this.classes.isEmpty()) 517 { 518 String schemaName = ""; 519 if (StringUtils.isNotBlank(this.schema)) 520 { 521 schemaName = " '" + this.schema + "' "; 522 } 523 StringBuilder warning = new StringBuilder("WARNING! No tables found in schema"); 524 warning.append(schemaName); 525 if (StringUtils.isNotBlank(this.tableNamePattern)) 526 { 527 warning.append(" matching pattern --> '").append(this.tableNamePattern).append('\''); 528 } 529 logger.warn(warning); 530 } 531 532 // add all attributes and associations to the modelPackage 533 for (String tableName : this.classes.keySet()) 534 { 535 final UmlClass umlClass = classes.get(tableName); 536 if (logger.isInfoEnabled()) 537 { 538 logger.info("created class --> '" + umlClass.getName() + '\''); 539 } 540 541 // create and add all associations to the package 542 modelPackage.getOwnedElement().addAll(this.createAssociations(metadata, corePackage, tableName)); 543 544 // create and add all the attributes 545 umlClass.getFeature().addAll(this.createAttributes(metadata, corePackage, tableName)); 546 547 modelPackage.getOwnedElement().add(umlClass); 548 } 549 } 550 551 /** 552 * Creates and returns a UmlClass with the given <code>name</code> using 553 * the <code>corePackage</code> to create it. 554 * @param modelPackage 555 * @param metadata 556 * @param corePackage used to create the class. 557 * @param tableName to tableName for which we'll create the appropriate 558 * class. 559 * @return the UmlClass 560 */ 561 protected UmlClass createClass( 562 org.omg.uml.modelmanagement.UmlPackage modelPackage, 563 DatabaseMetaData metadata, 564 CorePackage corePackage, 565 String tableName) 566 { 567 String className = SqlToModelNameFormatter.toClassName(tableName); 568 UmlClass umlClass = 569 corePackage.getUmlClass().createUmlClass( 570 className, VisibilityKindEnum.VK_PUBLIC, false, false, false, false, false); 571 572 umlClass.getStereotype().addAll(this.getOrCreateStereotypes(corePackage, 573 this.classStereotypes, "Classifier")); 574 575 if (StringUtils.isNotBlank(this.tableTaggedValue)) 576 { 577 // add the tagged value for the table name 578 TaggedValue taggedValue = this.createTaggedValue(corePackage, 579 this.tableTaggedValue, tableName); 580 if (taggedValue != null) 581 { 582 umlClass.getTaggedValue().add(taggedValue); 583 } 584 } 585 586 return umlClass; 587 } 588 589 /** 590 * Creates and returns a collection of attributes from creating an attribute 591 * from every column on the table having the give <code>tableName</code>. 592 * 593 * @param metadata the DatabaseMetaData from which to retrieve the columns. 594 * @param corePackage used to create the class. 595 * @param tableName the tableName for which to find columns. 596 * @return the collection of new attributes. 597 * @throws SQLException 598 */ 599 protected Collection createAttributes( 600 DatabaseMetaData metadata, 601 CorePackage corePackage, 602 String tableName) 603 throws SQLException 604 { 605 final Collection attributes = new ArrayList(); 606 final ResultSet columnRs = metadata.getColumns(null, this.schema, tableName, null); 607 final Collection primaryKeyColumns = this.getPrimaryKeyColumns(metadata, tableName); 608 609 ResultSetMetaData colMeta = columnRs.getMetaData(); 610 int colCount = colMeta.getColumnCount(); 611 612 while (columnRs.next()) 613 { 614 final String columnName = columnRs.getString("COLUMN_NAME"); 615 616 if (logger.isDebugEnabled()) { 617 for (int c = 1; c <= colCount; c++) { 618 logger.debug("Meta column " + colMeta.getColumnName(c) + " = " + columnRs.getString(c)); 619 } 620 } 621 622 if (this.columnNamePattern == null || columnName.matches(this.columnNamePattern)) 623 { 624 final String attributeName = SqlToModelNameFormatter.toAttributeName(columnName); 625 if (logger.isInfoEnabled()) 626 { 627 logger.info("adding attribute --> '" + attributeName + '\''); 628 } 629 630 // do NOT add foreign key columns as attributes (since 631 // they are placed on association ends) 632 if (!this.hasForeignKey(tableName, columnName)) 633 { 634 Classifier typeClass = null; 635 636 // first we try to find a mapping that maps to the 637 // database proprietary type 638 String typeName = columnRs.getString(this.getMetaColumnTypeName()); 639 String colSize = columnRs.getString(this.getMetaColumnColumnSize()); 640 String decPlaces = null; 641 if (StringUtils.isNotBlank(this.getMetaColumnDecPlaces())) 642 { 643 decPlaces = columnRs.getString(this.getMetaColumnDecPlaces()); 644 } 645 646 String type = 647 Schema2XMIUtils.constructTypeName( 648 typeName, 649 colSize, decPlaces); 650 logger.info(" - searching for type mapping '" + type + '\''); 651 if (typeMappings.containsFrom(type)) 652 { 653 typeClass = this.getOrCreateDataType(corePackage, type); 654 } 655 656 // - See if we can find a type matching a mapping for a JDBC type 657 // (if we haven't found a database specific one) 658 if (typeClass == null) 659 { 660 type = JdbcTypeFinder.find(columnRs.getInt("DATA_TYPE")); 661 logger.info(" - searching for type mapping '" + type + '\''); 662 if (typeMappings.containsFrom(type)) 663 { 664 typeClass = this.getOrCreateDataType(corePackage, type); 665 } 666 else 667 { 668 logger.warn(" ! no mapping found, type not added to '" + attributeName + '\''); 669 } 670 } 671 672 boolean required = !this.isColumnNullable(metadata, tableName, columnName); 673 674 Attribute attribute = 675 corePackage.getAttribute().createAttribute( 676 attributeName, 677 VisibilityKindEnum.VK_PUBLIC, 678 false, 679 ScopeKindEnum.SK_INSTANCE, 680 this.createAttributeMultiplicity( 681 corePackage.getDataTypes(), 682 required), 683 ChangeableKindEnum.CK_CHANGEABLE, 684 ScopeKindEnum.SK_CLASSIFIER, 685 OrderingKindEnum.OK_UNORDERED, 686 null); 687 attribute.setType(typeClass); 688 689 if (StringUtils.isNotBlank(this.columnTaggedValue)) 690 { 691 // add the tagged value for the column name 692 TaggedValue taggedValue = 693 this.createTaggedValue(corePackage, this.columnTaggedValue, columnName); 694 if (taggedValue != null) 695 { 696 attribute.getTaggedValue().add(taggedValue); 697 } 698 } 699 700 // Add the attribute specific tagged values (if any)... 701 if (!attributeTaggedValues.isEmpty()) 702 { 703 Set<String> keys = attributeTaggedValues.keySet(); 704 for (final String tag : keys) 705 { 706 final String value = attributeTaggedValues.get(tag); 707 TaggedValue taggedValue = 708 this.createTaggedValue(corePackage, tag, value); 709 if (taggedValue != null) 710 { 711 attribute.getTaggedValue().add(taggedValue); 712 } 713 } 714 } 715 716 if (primaryKeyColumns.contains(columnName)) 717 { 718 attribute.getStereotype().addAll( 719 this.getOrCreateStereotypes(corePackage, this.identifierStereotypes, "Attribute")); 720 } 721 attributes.add(attribute); 722 } 723 } 724 } 725 DbUtils.closeQuietly(columnRs); 726 return attributes; 727 } 728 729 /** 730 * @param metaColumnDecPlaces 731 */ 732 public void setMetaColumnDecPlaces(String metaColumnDecPlaces) { 733 this.metaColumnDecPlaces = metaColumnDecPlaces; 734 } 735 736 /** 737 * @return metaColumnDecPlaces 738 */ 739 public String getMetaColumnDecPlaces() { 740 return metaColumnDecPlaces; 741 } 742 743 /** 744 * @param metaColumnColumnSize 745 */ 746 public void setMetaColumnColumnSize(String metaColumnColumnSize) { 747 this.metaColumnColumnSize = metaColumnColumnSize; 748 } 749 750 /** 751 * @return metaColumnColumnSize 752 */ 753 public String getMetaColumnColumnSize() { 754 return metaColumnColumnSize; 755 } 756 757 /** 758 * @param metaColumnTypeName 759 */ 760 public void setMetaColumnTypeName(String metaColumnTypeName) { 761 this.metaColumnTypeName = metaColumnTypeName; 762 } 763 764 /** 765 * @return metaColumnTypeName 766 */ 767 public String getMetaColumnTypeName() { 768 return metaColumnTypeName; 769 } 770 771 /** 772 * Gets or creates a new data type instance having the given fully qualified 773 * <code>type</code> name. 774 * 775 * @param corePackage the core package 776 * @param type the fully qualified type name. 777 * @return the DataType 778 */ 779 protected DataType getOrCreateDataType( 780 CorePackage corePackage, 781 String type) 782 { 783 type = this.typeMappings.getTo(type); 784 Object datatype = ModelElementFinder.find(this.model, type); 785 if (datatype == null || !DataType.class.isAssignableFrom(datatype.getClass())) 786 { 787 String[] names = type.split(Schema2XMIGlobals.PACKAGE_SEPARATOR); 788 if (names != null && names.length > 0) 789 { 790 // the last name is the type name 791 String typeName = names[names.length - 1]; 792 names[names.length - 1] = null; 793 String packageName = StringUtils.join(names, Schema2XMIGlobals.PACKAGE_SEPARATOR); 794 org.omg.uml.modelmanagement.UmlPackage umlPackage = 795 this.getOrCreatePackage( 796 this.umlPackage.getModelManagement(), 797 this.model, 798 packageName); 799 if (umlPackage != null) 800 { 801 datatype = 802 corePackage.getDataType().createDataType( 803 typeName, VisibilityKindEnum.VK_PUBLIC, false, false, false, false); 804 umlPackage.getOwnedElement().add(datatype); 805 } 806 } 807 } 808 return (DataType)datatype; 809 } 810 811 /** 812 * This method just checks to see if a column is null able or not, if so, 813 * returns true, if not returns false. 814 * 815 * @param metadata the DatabaseMetaData instance used to retrieve the column 816 * information. 817 * @param tableName the name of the table on which the column exists. 818 * @param columnName the name of the column. 819 * @return true/false on whether or not column is nullable. 820 * @throws SQLException 821 */ 822 protected boolean isColumnNullable( 823 DatabaseMetaData metadata, 824 String tableName, 825 String columnName) 826 throws SQLException 827 { 828 boolean nullable = true; 829 ResultSet columnRs = metadata.getColumns(null, this.schema, tableName, columnName); 830 while (columnRs.next()) 831 { 832 nullable = columnRs.getInt("NULLABLE") != DatabaseMetaData.attributeNoNulls; 833 } 834 DbUtils.closeQuietly(columnRs); 835 return nullable; 836 } 837 838 /** 839 * Returns a collection of all primary key column names for the given 840 * <code>tableName</code>. 841 * 842 * @param metadata 843 * @param tableName 844 * @return collection of primary key names. 845 * @throws SQLException 846 */ 847 protected Collection getPrimaryKeyColumns( 848 DatabaseMetaData metadata, 849 String tableName) 850 throws SQLException 851 { 852 Collection primaryKeys = new HashSet(); 853 ResultSet primaryKeyRs = metadata.getPrimaryKeys(null, this.schema, tableName); 854 while (primaryKeyRs.next()) 855 { 856 primaryKeys.add(primaryKeyRs.getString("COLUMN_NAME")); 857 } 858 DbUtils.closeQuietly(primaryKeyRs); 859 return primaryKeys; 860 } 861 862 /** 863 * Creates and returns a collection of associations by determining foreign 864 * tables to the table having the given <code>tableName</code>. 865 * 866 * @param metadata the DatabaseMetaData from which to retrieve the columns. 867 * @param corePackage used to create the class. 868 * @param tableName the tableName for which to find columns. 869 * @return the collection of new attributes. 870 * @throws SQLException 871 */ 872 protected Collection createAssociations( 873 DatabaseMetaData metadata, 874 CorePackage corePackage, 875 String tableName) 876 throws SQLException 877 { 878 Collection primaryKeys = this.getPrimaryKeyColumns(metadata, tableName); 879 Collection associations = new ArrayList(); 880 ResultSet columnRs = metadata.getImportedKeys(null, this.schema, tableName); 881 while (columnRs.next()) 882 { 883 // store the foreign key in the foreignKeys Map 884 String fkColumnName = columnRs.getString("FKCOLUMN_NAME"); 885 this.addForeignKey(tableName, fkColumnName); 886 887 // now create the association 888 String foreignTableName = columnRs.getString("PKTABLE_NAME"); 889 UmlAssociation association = 890 corePackage.getUmlAssociation().createUmlAssociation( 891 null, VisibilityKindEnum.VK_PUBLIC, false, false, false, false); 892 893 // we set the upper range to 1 if the 894 // they primary key of this table is the 895 // foreign key of another table (by default 896 // its set to a many multiplicity) 897 int primaryUpper = -1; 898 if (primaryKeys.contains(fkColumnName)) 899 { 900 primaryUpper = 1; 901 } 902 903 String endName = null; 904 905 // primary association 906 AssociationEnd primaryEnd = 907 corePackage.getAssociationEnd().createAssociationEnd( 908 endName, 909 VisibilityKindEnum.VK_PUBLIC, 910 false, 911 true, 912 OrderingKindEnum.OK_UNORDERED, 913 AggregationKindEnum.AK_NONE, 914 ScopeKindEnum.SK_INSTANCE, 915 this.createMultiplicity( 916 corePackage.getDataTypes(), 917 0, 918 primaryUpper), 919 ChangeableKindEnum.CK_CHANGEABLE); 920 primaryEnd.setParticipant(this.classes.get(tableName)); 921 association.getConnection().add(primaryEnd); 922 923 boolean required = !this.isColumnNullable(metadata, tableName, fkColumnName); 924 925 int foreignLower = 0; 926 if (required) 927 { 928 foreignLower = 1; 929 } 930 931 int deleteRule = columnRs.getInt("DELETE_RULE"); 932 933 // determine if we should have composition for 934 // the foreign association end depending on cascade delete 935 AggregationKindEnum foreignAggregation = AggregationKindEnum.AK_NONE; 936 if (deleteRule == DatabaseMetaData.importedKeyCascade) 937 { 938 foreignAggregation = AggregationKindEnum.AK_COMPOSITE; 939 } 940 941 // foreign association 942 AssociationEnd foreignEnd = 943 corePackage.getAssociationEnd().createAssociationEnd( 944 endName, 945 VisibilityKindEnum.VK_PUBLIC, 946 false, 947 true, 948 OrderingKindEnum.OK_UNORDERED, 949 foreignAggregation, 950 ScopeKindEnum.SK_INSTANCE, 951 this.createMultiplicity( 952 corePackage.getDataTypes(), 953 foreignLower, 954 1), 955 ChangeableKindEnum.CK_CHANGEABLE); 956 final Classifier foreignParticipant = this.classes.get(foreignTableName); 957 if (foreignParticipant == null) 958 { 959 throw new SchemaTransformerException( 960 "The associated table '" + foreignTableName + 961 "' must be available in order to create the association"); 962 } 963 foreignEnd.setParticipant(foreignParticipant); 964 965 if (StringUtils.isNotBlank(this.columnTaggedValue)) 966 { 967 // add the tagged value for the foreign association end 968 TaggedValue taggedValue = this.createTaggedValue(corePackage, 969 this.columnTaggedValue, fkColumnName); 970 if (taggedValue != null) 971 { 972 foreignEnd.getTaggedValue().add(taggedValue); 973 } 974 } 975 976 association.getConnection().add(foreignEnd); 977 associations.add(association); 978 979 if (logger.isInfoEnabled()) 980 { 981 logger.info( 982 "adding association: '" + primaryEnd.getParticipant().getName() + " <--> " + 983 foreignEnd.getParticipant().getName() + '\''); 984 } 985 } 986 DbUtils.closeQuietly(columnRs); 987 return associations; 988 } 989 990 /** 991 * Creates a tagged value given the specified <code>name</code>. 992 * @param corePackage 993 * @param name the name of the tagged value to create. 994 * @param value the value to populate on the tagged value. 995 * @return returns the new TaggedValue 996 */ 997 protected TaggedValue createTaggedValue( 998 CorePackage corePackage, 999 String name, 1000 String value) 1001 { 1002 Collection values = new HashSet(); 1003 values.add(value); 1004 TaggedValue taggedValue = 1005 corePackage.getTaggedValue().createTaggedValue(name, VisibilityKindEnum.VK_PUBLIC, false, values); 1006 1007 // see if we can find the tag definition and if so add that 1008 // as the type. 1009 Object tagDefinition = ModelElementFinder.find(this.umlPackage, name); 1010 if (tagDefinition != null && TagDefinition.class.isAssignableFrom(tagDefinition.getClass())) 1011 { 1012 taggedValue.setType((TagDefinition)tagDefinition); 1013 } 1014 return taggedValue; 1015 } 1016 1017 /** 1018 * Gets or creates a stereotypes given the specified comma separated list of 1019 * <code>names</code>. If any of the stereotypes can't be found, they 1020 * will be created. 1021 * @param corePackage 1022 * @param names comma separated list of stereotype names 1023 * @param baseClass the base class for which the stereotype applies. 1024 * @return Collection of Stereotypes 1025 */ 1026 protected Collection getOrCreateStereotypes( 1027 CorePackage corePackage, 1028 String names, 1029 String baseClass) 1030 { 1031 Collection stereotypes = new HashSet(); 1032 String[] stereotypeNames = null; 1033 if (names != null) 1034 { 1035 stereotypeNames = names.split(","); 1036 } 1037 if (stereotypeNames != null && stereotypeNames.length > 0) 1038 { 1039 for (int ctr = 0; ctr < stereotypeNames.length; ctr++) 1040 { 1041 String name = StringUtils.trimToEmpty(stereotypeNames[ctr]); 1042 1043 // see if we can find the stereotype first 1044 Object stereotype = ModelElementFinder.find(this.umlPackage, name); 1045 if (stereotype == null || !Stereotype.class.isAssignableFrom(stereotype.getClass())) 1046 { 1047 Collection baseClasses = new ArrayList(); 1048 baseClasses.add(baseClass); 1049 stereotype = 1050 corePackage.getStereotype().createStereotype( 1051 name, VisibilityKindEnum.VK_PUBLIC, false, false, false, false, null, baseClasses); 1052 this.model.getOwnedElement().add(stereotype); 1053 } 1054 stereotypes.add(stereotype); 1055 } 1056 } 1057 return stereotypes; 1058 } 1059 1060 /** 1061 * Adds a foreign key column name to the <code>foreignKeys</code> Map. The 1062 * map stores a collection of foreign key names keyed by the given 1063 * <code>tableName</code> 1064 * 1065 * @param tableName the name of the table for which to store the keys. 1066 * @param columnName the name of the foreign key column name. 1067 */ 1068 protected void addForeignKey( 1069 String tableName, 1070 String columnName) 1071 { 1072 if (StringUtils.isNotBlank(tableName) && StringUtils.isNotBlank(columnName)) 1073 { 1074 Collection foreignKeys = (Collection)this.foreignKeys.get(tableName); 1075 if (foreignKeys == null) 1076 { 1077 foreignKeys = new HashSet(); 1078 } 1079 foreignKeys.add(columnName); 1080 this.foreignKeys.put(tableName, foreignKeys); 1081 } 1082 } 1083 1084 /** 1085 * Returns true if the table with the given <code>tableName</code> has a 1086 * foreign key with the specified <code>columnName</code>. 1087 * 1088 * @param tableName the name of the table to check for the foreign key 1089 * @param columnName the name of the foreign key column. 1090 * @return true/false depending on whether or not the table has the foreign 1091 * key with the given <code>columnName</code>. 1092 */ 1093 protected boolean hasForeignKey( 1094 String tableName, 1095 String columnName) 1096 { 1097 boolean hasForeignKey = false; 1098 if (StringUtils.isNotBlank(tableName) && StringUtils.isNotBlank(columnName)) 1099 { 1100 Collection foreignKeys = (Collection)this.foreignKeys.get(tableName); 1101 if (foreignKeys != null) 1102 { 1103 hasForeignKey = foreignKeys.contains(columnName); 1104 } 1105 } 1106 return hasForeignKey; 1107 } 1108 1109 /** 1110 * Creates an attributes multiplicity, if <code>required</code> is true, 1111 * then multiplicity is set to 1, if <code>required</code> is false, then 1112 * multiplicity is set to 0..1. 1113 * 1114 * @param dataTypes used to create the Multiplicity 1115 * @param required whether or not the attribute is required therefore 1116 * determining the multiplicity value created. 1117 * @return the new Multiplicity 1118 */ 1119 protected Multiplicity createAttributeMultiplicity( 1120 DataTypesPackage dataTypes, 1121 boolean required) 1122 { 1123 Multiplicity mult = null; 1124 if (required) 1125 { 1126 mult = this.createMultiplicity(dataTypes, 1, 1); 1127 } 1128 else 1129 { 1130 mult = this.createMultiplicity(dataTypes, 0, 1); 1131 } 1132 return mult; 1133 } 1134 1135 /** 1136 * Creates a multiplicity, from <code>lower</code> and <code>upper</code> 1137 * ranges. 1138 * 1139 * @param dataTypes used to create the Multiplicity 1140 * @param lower the lower range of the multiplicity 1141 * @param upper the upper range of the multiplicity 1142 * @return the new Multiplicity 1143 */ 1144 protected Multiplicity createMultiplicity( 1145 DataTypesPackage dataTypes, 1146 int lower, 1147 int upper) 1148 { 1149 Multiplicity mult = dataTypes.getMultiplicity().createMultiplicity(); 1150 MultiplicityRange range = dataTypes.getMultiplicityRange().createMultiplicityRange(lower, upper); 1151 mult.getRange().add(range); 1152 return mult; 1153 } 1154}