View Javadoc

1   package org.andromda.schema2xmi;
2   
3   import java.sql.Connection;
4   import java.sql.DatabaseMetaData;
5   import java.sql.DriverManager;
6   import java.sql.ResultSet;
7   import java.sql.SQLException;
8   
9   import java.util.ArrayList;
10  import java.util.Collection;
11  import java.util.HashMap;
12  import java.util.HashSet;
13  import java.util.Iterator;
14  import java.util.Map;
15  
16  import org.andromda.core.common.ExceptionUtils;
17  import org.andromda.core.engine.ModelProcessorException;
18  import org.andromda.core.mapping.Mappings;
19  import org.andromda.core.namespace.NamespaceComponents;
20  import org.andromda.core.repository.Repositories;
21  import org.andromda.core.repository.RepositoryFacade;
22  import org.apache.commons.dbutils.DbUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.log4j.Logger;
25  import org.omg.uml.UmlPackage;
26  import org.omg.uml.foundation.core.AssociationEnd;
27  import org.omg.uml.foundation.core.Attribute;
28  import org.omg.uml.foundation.core.Classifier;
29  import org.omg.uml.foundation.core.CorePackage;
30  import org.omg.uml.foundation.core.DataType;
31  import org.omg.uml.foundation.core.Stereotype;
32  import org.omg.uml.foundation.core.TagDefinition;
33  import org.omg.uml.foundation.core.TaggedValue;
34  import org.omg.uml.foundation.core.UmlAssociation;
35  import org.omg.uml.foundation.core.UmlClass;
36  import org.omg.uml.foundation.datatypes.AggregationKindEnum;
37  import org.omg.uml.foundation.datatypes.ChangeableKindEnum;
38  import org.omg.uml.foundation.datatypes.DataTypesPackage;
39  import org.omg.uml.foundation.datatypes.Multiplicity;
40  import org.omg.uml.foundation.datatypes.MultiplicityRange;
41  import org.omg.uml.foundation.datatypes.OrderingKindEnum;
42  import org.omg.uml.foundation.datatypes.ScopeKindEnum;
43  import org.omg.uml.foundation.datatypes.VisibilityKindEnum;
44  import org.omg.uml.modelmanagement.Model;
45  import org.omg.uml.modelmanagement.ModelManagementPackage;
46  
47  
48  /***
49   * Performs the transformation of database schema to XMI.
50   *
51   * @todo This class really should have the functionality it uses (writing model
52   *       elements) moved to the metafacades.
53   * @todo This class should be refactored into smaller classes.
54   * @author Chad Brandon
55   */
56  public class SchemaTransformer
57  {
58      private final static Logger logger = Logger.getLogger(SchemaTransformer.class);
59      private RepositoryFacade repository = null;
60  
61      /***
62       * The JDBC driver class
63       */
64      private String jdbcDriver = null;
65  
66      /***
67       * The JDBC schema user.
68       */
69      private String jdbcUser = null;
70  
71      /***
72       * The JDBC schema password.
73       */
74      private String jdbcPassword = null;
75  
76      /***
77       * The JDBC connection URL.
78       */
79      private String jdbcConnectionUrl = null;
80  
81      /***
82       * The name of the package in which the name of the elements will be
83       * created.
84       */
85      privateong> String packageName = null;
86  
87      /***
88       * Stores the name of the schema where the tables can be found.
89       */
90      private String schema = null;
91  
92      /***
93       * The regular expression pattern to match on when deciding what table names
94       * to add to the transformed XMI.
95       */
96      private String tableNamePattern = null;
97  
98      /***
99       * 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 classes = new HashMap();
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      * Stores the version of XMI that will be produced.
132      */
133     private String xmiVersion = null;
134 
135     /***
136      * Constructs a new instance of this SchemaTransformer.
137      */
138     public SchemaTransformer(
139         String jdbcDriver,
140         String jdbcConnectionUrl,
141         String jdbcUser,
142         String jdbcPassword)
143     {
144         ExceptionUtils.checkEmpty("jdbcDriver", jdbcDriver);
145         ExceptionUtils.checkEmpty("jdbcConnectionUrl", jdbcConnectionUrl);
146         ExceptionUtils.checkEmpty("jdbcUser", jdbcUser);
147         ExceptionUtils.checkEmpty("jdbcPassword", jdbcPassword);
148 
149         NamespaceComponents.instance().discover();
150         Repositories.instance().initialize();
151         this.repository = Repositories.instance().getImplementation(Schema2XMIGlobals.REPOSITORY_NAMESPACE_NETBEANSMDR);
152         if (repository == null)
153         {
154             throw new ModelProcessorException(
155                 "No Repository could be found, please make sure you have a repository with namespace " + Schema2XMIGlobals.REPOSITORY_NAMESPACE_NETBEANSMDR +
156                 " on your classpath");
157         }
158         this.repository.open();
159 
160         this.jdbcDriver = jdbcDriver;
161         this.jdbcConnectionUrl = jdbcConnectionUrl;
162         this.jdbcUser = jdbcUser;
163         this.jdbcPassword = jdbcPassword;
164         this.jdbcConnectionUrl = jdbcConnectionUrl;
165     }
166 
167     /***
168      * Transforms the Schema file and writes it to the location given by
169      * <code>outputLocation</code>. The <code>inputModel</code> must be a
170      * valid URL, otherwise an exception will be thrown.
171      *
172      * @param inputModel the location of the input model to start with (if there
173      *        is one)
174      * @param outputLocation The location to where the transformed output will
175      *        be written.
176      */
177     public void transform(
178         String inputModel,
179         String outputLocation)
180     {
181         long startTime = System.currentTimeMillis();
182         outputLocation = StringUtils.trimToEmpty(outputLocation);
183         if (outputLocation == null)
184         {
185             throw new IllegalArgumentException("'outputLocation' can not be null");
186         }
187         Connection connection = null;
188         try
189         {
190             if (inputModel != null)
191             {
192                 logger.info("Input model --> '" + inputModel + "'");
193             }
194             this.repository.readModel(
195                 new String[] {inputModel},
196                 null);
197             Class.forName(this.jdbcDriver);
198             connection = DriverManager.getConnection(this.jdbcConnectionUrl, this.jdbcUser, this.jdbcPassword);
199             repository.writeModel(
200                 transform(connection),
201                 outputLocation,
202                 this.xmiVersion);
203         }
204         catch (Throwable th)
205         {
206             throw new SchemaTransformerException(th);
207         }
208         finally
209         {
210             DbUtils.closeQuietly(connection);
211             repository.close();
212         }
213         logger.info(
214             "Completed adding " + this.classes.size() + " classes, writing model to --> '" + outputLocation +
215             "', TIME --> " + ((System.currentTimeMillis() - startTime) / 1000.0) + "[s]");
216     }
217 
218     /***
219      * Sets the <code>mappingsUri</code> which is the URI to the sql types to
220      * model type mappings.
221      *
222      * @param typeMappingsUri The typeMappings to set.
223      */
224     public void setTypeMappings(String typeMappingsUri)
225     {
226         try
227         {
228             this.typeMappings = Mappings.getInstance(typeMappingsUri);
229         }
230         catch (final Throwable throwable)
231         {
232             throw new SchemaTransformerException(throwable);
233         }
234     }
235 
236     /***
237      * Sets the name of the package to which the model elements will be created.
238      *
239      * @param packageName The packageName to set.
240      */
241     publicong> void setPackageName(String packageName)
242     {
243         this.packageName = packageName;
244     }
245 
246     /***
247      * Sets the name of the schema (where the tables can be found).
248      *
249      * @param schema The schema to set.
250      */
251     public void setSchema(String schema)
252     {
253         this.schema = schema;
254     }
255 
256     /***
257      * Sets the regular expression pattern to match on when deciding what table
258      * names to add to the transformed XMI.
259      *
260      * @param tableNamePattern The tableNamePattern to set.
261      */
262     public void setTableNamePattern(String tableNamePattern)
263     {
264         this.tableNamePattern = StringUtils.trimToEmpty(tableNamePattern);
265     }
266 
267     /***
268      * The column name pattern.
269      */
270     private String columnNamePattern;
271 
272     /***
273      * Sets the regular expression pattern to match on when deciding what attributes
274      * ti create in the XMI.
275      *
276      * @param columnNamePattern The pattern for filtering the column name.
277      */
278     public void setColumnNamePattern(String columnNamePattern)
279     {
280         this.columnNamePattern = columnNamePattern;
281     }
282 
283     /***
284      * Sets the stereotype name for the new classes.
285      *
286      * @param classStereotypes The classStereotypes to set.
287      */
288     public void setClassStereotypes(String classStereotypes)
289     {
290         this.classStereotypes = StringUtils.deleteWhitespace(classStereotypes);
291     }
292 
293     /***
294      * Specifies the identifier stereotype.
295      */
296     private String identifierStereotypes = null;
297 
298     /***
299      * Sets the stereotype name for the identifiers on the new classes.
300      *
301      * @param identifierStereotypes The identifierStereotypes to set.
302      */
303     public void setIdentifierStereotypes(String identifierStereotypes)
304     {
305         this.identifierStereotypes = StringUtils.deleteWhitespace(identifierStereotypes);
306     }
307 
308     /***
309      * Sets the name of the column tagged value to use for storing the name of
310      * the column.
311      *
312      * @param columnTaggedValue The columnTaggedValue to set.
313      */
314     public void setColumnTaggedValue(String columnTaggedValue)
315     {
316         this.columnTaggedValue = StringUtils.trimToEmpty(columnTaggedValue);
317     }
318 
319     /***
320      * Sets the name of the table tagged value to use for storing the name of
321      * the table.
322      *
323      * @param tableTaggedValue The tableTaggedValue to set.
324      */
325     public void setTableTaggedValue(String tableTaggedValue)
326     {
327         this.tableTaggedValue = StringUtils.trimToEmpty(tableTaggedValue);
328     }
329 
330     /***
331      * Sets the version of XMI that will be produced.
332      *
333      * @param xmiVersion The xmiVersion to set.
334      */
335     public void setXmiVersion(String xmiVersion)
336     {
337         this.xmiVersion = xmiVersion;
338     }
339 
340     /***
341      * The package that is currently being processed.
342      */
343     private UmlPackage umlPackage;
344 
345     /***
346      * The model thats currently being processed
347      */
348     private Model model;
349 
350     /***
351      * Performs the actual translation of the Schema to the XMI and returns the
352      * object model.
353      */
354     private final Object transform(final Connection connection)
355         throws Exception
356     {
357         this.umlPackage = (UmlPackage)this.repository.getModel().getModel();
358 
359         final ModelManagementPackage modelManagementPackage = umlPackage.getModelManagement();
360 
361         final Collection models = modelManagementPackage.getModel().refAllOfType();
362         if (models != null && !models.isEmpty())
363         {
364             // A given XMI file can contain multiple models.
365             // Use the first model in the XMI file
366             this.model = (Model)models.iterator().next();
367         }
368         else
369         {
370             this.model = modelManagementPackage.getModel().createModel();
371         }
372 
373         // create the package on the model
374         org.omg.uml.modelmanagement.UmlPackage leafPackage =
375             this.getOrCreatePackage(
376                 umlPackage.getModelManagement(),
377                 model,
378                 this.packageName);
379         this.createClasses(
380             connection,
381             umlPackage.getModelManagement().getCore(),
382             leafPackage);
383 
384         return umlPackage;
385     }
386 
387     /***
388      * Gets or creates a package having the specified <code>packageName</code>
389      * using the given <code>modelManagementPackage</code>, places it on the
390      * <code>model</code> and returns the last leaf package.
391      *
392      * @param modelManagementPackage from which we retrieve the UmlPackageClass
393      *        to create a UmlPackage.
394      * @param modelPackage the root UmlPackage
395      */
396     protected org.omg.uml.modelmanagement.UmlPackage getOrCreatePackage(
397         ModelManagementPackage modelManagementPackage,
398         org.omg.uml.modelmanagement.UmlPackage modelPackage,
399         String packageName)
400     {
401         packageName = StringUtils.trimToEmpty(packageName);
402         if</strong> (StringUtils.isNotEmpty(packageName))
403         {
404             String[] packages = packageName.split(Schema2XMIGlobals.PACKAGE_SEPERATOR);
405             if (packages != null && packages.length > 0)
406             {
407                 for (int ctr = 0; ctr < packages.length; ctr++)
408                 {
409                     Object umlPackage = ModelElementFinder.find(modelPackage, packages[ctr]);
410 
411                     if (umlPackage == null)
412                     {
413                         umlPackage =
414                             modelManagementPackage.getUmlPackage().createUmlPackage(
415                                 packages[ctr], VisibilityKindEnum.VK_PUBLIC, false, false, false, false);
416                         modelPackage.getOwnedElement().add(umlPackage);
417                     }
418                     modelPackage = (org.omg.uml.modelmanagement.UmlPackage)umlPackage;
419                 }
420             }
421         }
422         return modelPackage;
423     }
424 
425     /***
426      * Creates all classes from the tables found in the schema.
427      *
428      * @param connection the Connection used to retrieve the schema metadata.
429      * @param corePackage the CorePackage instance we use to create the classes.
430      * @param modelPackage the package which the classes are added.
431      */
432     protected void createClasses(
433         Connection connection,
434         CorePackage corePackage,
435         org.omg.uml.modelmanagement.UmlPackage modelPackage)
436         throws SQLException
437     {
438         DatabaseMetaData metadata = connection.getMetaData();
439         ResultSet tableRs = metadata.getTables(
440                 null,
441                 this.schema,
442                 null,
443                 new String[] {"TABLE"});
444 
445         // loop through and create all classes and store then
446         // in the classes Map keyed by table
447         while (tableRs.next())
448         {
449             String tableName = tableRs.getString("TABLE_NAME");
450             if (StringUtils.isNotBlank(this.tableNamePattern))
451             {
452                 if (tableName.matches(this.tableNamePattern))
453                 {
454                     UmlClass umlClass = this.createClass(modelPackage, metadata, corePackage, tableName);
455                     this.classes.put(tableName, umlClass);
456                 }
457             }
458             else
459             {
460                 UmlClass umlClass = this.createClass(modelPackage, metadata, corePackage, tableName);
461                 this.classes.put(tableName, umlClass);
462             }
463         }
464         DbUtils.closeQuietly(tableRs);
465         if (this.classes.isEmpty())
466         {
467             String schemaName = "";
468             if (StringUtils.isNotEmpty(this.schema))
469             {
470                 schemaName = " '" + this.schema + "' ";
471             }
472             StringBuffer warning = new StringBuffer("WARNING! No tables found in schema");
473             warning.append(schemaName);
474             if (StringUtils.isNotEmpty(this.tableNamePattern))
475             {
476                 warning.append(" matching pattern --> '" + this.tableNamePattern + "'");
477             }
478             logger.warn(warning);
479         }
480 
481         // add all attributes and associations to the modelPackage
482         Iterator tableNameIt = this.classes.keySet().iterator();
483         while (tableNameIt.hasNext())
484         {
485             String tableName = (String)tableNameIt.next();
486             UmlClass umlClass = (UmlClass)classes.get(tableName);
487             if (logger.isInfoEnabled())
488             {
489                 logger.info("created class --> '" + umlClass.getName() + "'");
490             }
491 
492             // create and add all associations to the package
493             modelPackage.getOwnedElement().addAll(this.createAssociations(metadata, corePackage, tableName));
494 
495             // create and add all the attributes
496             umlClass.getFeature().addAll(this.createAttributes(metadata, corePackage, tableName));
497 
498             modelPackage.getOwnedElement().add(umlClass);
499         }
500     }
501 
502     /***
503      * Creates and returns a UmlClass with the given <code>name</code> using
504      * the <code>corePackage</code> to create it.
505      *
506      * @param corePackage used to create the class.
507      * @param tableName to tableName for which we'll create the appropriate
508      *        class.
509      * @return the UmlClass
510      */
511     protected UmlClass createClass(
512         org.omg.uml.modelmanagement.UmlPackage modelPackage,
513         DatabaseMetaData metadata,
514         CorePackage corePackage,
515         String tableName)
516     {
517         String className = SqlToModelNameFormatter.toClassName(tableName);
518         UmlClass umlClass =
519             corePackage.getUmlClass().createUmlClass(
520                 className, VisibilityKindEnum.VK_PUBLIC, false, false, false, false, false);
521 
522         umlClass.getStereotype().addAll(this.getOrCreateStereotypes(corePackage, this.classStereotypes, "Classifier"));
523 
524         if (StringUtils.isNotEmpty(this.tableTaggedValue))
525         {
526             // add the tagged value for the table name
527             TaggedValue taggedValue = this.createTaggedValue(corePackage, this.tableTaggedValue, tableName);
528             if (taggedValue != null)
529             {
530                 umlClass.getTaggedValue().add(taggedValue);
531             }
532         }
533 
534         return umlClass;
535     }
536 
537     /***
538      * Creates and returns a collection of attributes from creating an attribute
539      * from every column on the table having the give <code>tableName</code>.
540      *
541      * @param metadata the DatabaseMetaData from which to retrieve the columns.
542      * @param corePackage used to create the class.
543      * @param tableName the tableName for which to find columns.
544      * @return the collection of new attributes.
545      */
546     protected Collection createAttributes(
547         DatabaseMetaData metadata,
548         CorePackage corePackage,
549         String tableName)
550         throws SQLException
551     {
552         final Collection attributes = new ArrayList();
553         final ResultSet columnRs = metadata.getColumns(null, this.schema, tableName, null);
554         final Collection primaryKeyColumns = this.getPrimaryKeyColumns(metadata, tableName);
555         while (columnRs.next())
556         {
557             final String columnName = columnRs.getString("COLUMN_NAME");
558             if (this.columnNamePattern == null || columnName.matches(this.columnNamePattern))
559             {
560                 final String attributeName = SqlToModelNameFormatter.toAttributeName(columnName);
561                 if (logger.isInfoEnabled())
562                 {
563                     logger.info("adding attribute --> '" + attributeName + "'");
564                 }
565 
566                 // do NOT add foreign key columns as attributes (since
567                 // they are placed on association ends)
568                 if (!this.hasForeignKey(tableName, columnName))
569                 {
570                     Classifier typeClass = null;
571 
572                     // first we try to find a mapping that maps to the
573                     // database proprietary type
574                     String type =
575                         Schema2XMIUtils.constructTypeName(
576                             columnRs.getString("TYPE_NAME"),
577                             columnRs.getString("COLUMN_SIZE"));
578                     logger.info("  -  searching for type mapping '" + type + "'");
579                     if (typeMappings.containsFrom(type))
580                     {
581                         typeClass = this.getOrCreateDataType(corePackage, type);
582                     }
583 
584                     // - See if we can find a type matching a mapping for a JDBC type 
585                     //   (if we haven't found a database specific one)
586                     if (typeClass == null)
587                     {
588                         type = JdbcTypeFinder.find(columnRs.getInt("DATA_TYPE"));
589                         logger.info("  -  searching for type mapping '" + type + "'");
590                         if (typeMappings.containsFrom(type))
591                         {
592                             typeClass = this.getOrCreateDataType(corePackage, type);
593                         }
594                         else
595                         {
596                             logger.info("  !  no mapping found, type not added to '" + attributeName + "'");
597                         }
598                     }
599 
600                     boolean required = !this.isColumnNullable(metadata, tableName, columnName);
601 
602                     Attribute attribute =
603                         corePackage.getAttribute().createAttribute(
604                             attributeName,
605                             VisibilityKindEnum.VK_PUBLIC,
606                             false,
607                             ScopeKindEnum.SK_INSTANCE,
608                             this.createAttributeMultiplicity(
609                                 corePackage.getDataTypes(),
610                                 required),
611                             ChangeableKindEnum.CK_CHANGEABLE,
612                             ScopeKindEnum.SK_CLASSIFIER,
613                             OrderingKindEnum.OK_UNORDERED,
614                             null);
615                     attribute.setType(typeClass);
616 
617                     if (StringUtils.isNotEmpty(this.columnTaggedValue))
618                     {
619                         // add the tagged value for the column name
620                         TaggedValue taggedValue =
621                             this.createTaggedValue(corePackage, this.columnTaggedValue, columnName);
622                         if (taggedValue != null)
623                         {
624                             attribute.getTaggedValue().add(taggedValue);
625                         }
626                     }
627                     if (primaryKeyColumns.contains(columnName))
628                     {
629                         attribute.getStereotype().addAll(
630                             this.getOrCreateStereotypes(corePackage, this.identifierStereotypes, "Attribute"));
631                     }
632                     attributes.add(attribute);
633                 }
634             }
635         }
636         DbUtils.closeQuietly(columnRs);
637         return attributes;
638     }
639 
640     /***
641      * Gets or creates a new data type instance having the given fully qualified
642      * <code>type</code> name.
643      *
644      * @param corePackage the core package
645      * @param type the fully qualified type name.
646      * @return the DataType
647      */
648     protected DataType getOrCreateDataType(
649         CorePackage corePackage,
650         String type)
651     {
652         type = this.typeMappings.getTo(type);
653         Object datatype = ModelElementFinder.find(this.model, type);
654         if (datatype == null || !DataType.class.isAssignableFrom(datatype.getClass()))
655         {
656             String[] names = type.split(Schema2XMIGlobals.PACKAGE_SEPERATOR);
657             if (names != null && names.length > 0)
658             {
659                 // the last name is the type name
660                 String typeName = names[names.length - 1];
661                 names[names.length - 1] = null;
662                 String packageName = StringUtils.join(names, Schema2XMIGlobals.PACKAGE_SEPERATOR);
663                 org.omg.uml.modelmanagement.UmlPackage umlPackage =
664                     this.getOrCreatePackage(
665                         this.umlPackage.getModelManagement(),
666                         this.model,
667                         packageName);
668                 if (umlPackage != null)
669                 {
670                     datatype =
671                         corePackage.getDataType().createDataType(
672                             typeName, VisibilityKindEnum.VK_PUBLIC, false, false, false, false);
673                     umlPackage.getOwnedElement().add(datatype);
674                 }
675             }
676         }
677         return (DataType)datatype;
678     }
679 
680     /***
681      * This method just checks to see if a column is null able or not, if so,
682      * returns true, if not returns false.
683      *
684      * @param metadata the DatabaseMetaData instance used to retrieve the column
685      *        information.
686      * @param tableName the name of the table on which the column exists.
687      * @param columnName the name of the column.
688      * @param true/false on whether or not column is nullable.
689      */
690     protected boolean isColumnNullable(
691         DatabaseMetaData metadata,
692         String tableName,
693         String columnName)
694         throws SQLException
695     {
696         boolean nullable = true;
697         ResultSet columnRs = metadata.getColumns(null, this.schema, tableName, columnName);
698         while (columnRs.next())
699         {
700             nullable = columnRs.getInt("NULLABLE") != DatabaseMetaData.attributeNoNulls;
701         }
702         DbUtils.closeQuietly(columnRs);
703         return nullable;
704     }
705 
706     /***
707      * Returns a collection of all primary key column names for the given
708      * <code>tableName</code>.
709      *
710      * @param metadata
711      * @param tableName
712      * @return collection of primary key names.
713      */
714     protected Collection getPrimaryKeyColumns(
715         DatabaseMetaData metadata,
716         String tableName)
717         throws SQLException
718     {
719         Collection primaryKeys = new HashSet();
720         ResultSet primaryKeyRs = metadata.getPrimaryKeys(null, this.schema, tableName);
721         while (primaryKeyRs.next())
722         {
723             primaryKeys.add(primaryKeyRs.getString("COLUMN_NAME"));
724         }
725         DbUtils.closeQuietly(primaryKeyRs);
726         return primaryKeys;
727     }
728 
729     /***
730      * Creates and returns a collection of associations by determing foreign
731      * tables to the table having the given <code>tableName</code>.
732      *
733      * @param metadata the DatabaseMetaData from which to retrieve the columns.
734      * @param corePackage used to create the class.
735      * @param tableName the tableName for which to find columns.
736      * @return the collection of new attributes.
737      */
738     protected Collection createAssociations(
739         DatabaseMetaData metadata,
740         CorePackage corePackage,
741         String tableName)
742         throws SQLException
743     {
744         Collection primaryKeys = this.getPrimaryKeyColumns(metadata, tableName);
745         Collection associations = new ArrayList();
746         ResultSet columnRs = metadata.getImportedKeys(null, this.schema, tableName);
747         while (columnRs.next())
748         {
749             // store the foreign key in the foreignKeys Map
750             String fkColumnName = columnRs.getString("FKCOLUMN_NAME");
751             this.addForeignKey(tableName, fkColumnName);
752 
753             // now create the association
754             String foreignTableName = columnRs.getString("PKTABLE_NAME");
755             UmlAssociation association =
756                 corePackage.getUmlAssociation().createUmlAssociation(
757                     null, VisibilityKindEnum.VK_PUBLIC, false, false, false, false);
758 
759             // we set the upper range to 1 if the
760             // they primary key of this table is the
761             // foreign key of another table (by default
762             // its set to a many multiplicity)
763             int primaryUpper = -1;
764             if (primaryKeys.contains(fkColumnName))
765             {
766                 primaryUpper = 1;
767             }
768 
769             String endName = null;
770 
771             // primary association
772             AssociationEnd primaryEnd =
773                 corePackage.getAssociationEnd().createAssociationEnd(
774                     endName,
775                     VisibilityKindEnum.VK_PUBLIC,
776                     false,
777                     true,
778                     OrderingKindEnum.OK_UNORDERED,
779                     AggregationKindEnum.AK_NONE,
780                     ScopeKindEnum.SK_INSTANCE,
781                     this.createMultiplicity(
782                         corePackage.getDataTypes(),
783                         0,
784                         primaryUpper),
785                     ChangeableKindEnum.CK_CHANGEABLE);
786             primaryEnd.setParticipant((Classifier)this.classes.get(tableName));
787             association.getConnection().add(primaryEnd);
788 
789             boolean required = !this.isColumnNullable(metadata, tableName, fkColumnName);
790 
791             int foreignLower = 0;
792             if (required)
793             {
794                 foreignLower = 1;
795             }
796 
797             int deleteRule = columnRs.getInt("DELETE_RULE");
798 
799             // determine if we should have composition for
800             // the foreign association end depending on cascade delete
801             AggregationKindEnum foreignAggregation = AggregationKindEnum.AK_NONE;
802             if (deleteRule == DatabaseMetaData.importedKeyCascade)
803             {
804                 foreignAggregation = AggregationKindEnum.AK_COMPOSITE;
805             }
806 
807             // foriegn association
808             AssociationEnd foreignEnd =
809                 corePackage.getAssociationEnd().createAssociationEnd(
810                     endName,
811                     VisibilityKindEnum.VK_PUBLIC,
812                     false,
813                     true,
814                     OrderingKindEnum.OK_UNORDERED,
815                     foreignAggregation,
816                     ScopeKindEnum.SK_INSTANCE,
817                     this.createMultiplicity(
818                         corePackage.getDataTypes(),
819                         foreignLower,
820                         1),
821                     ChangeableKindEnum.CK_CHANGEABLE);
822             final Classifier foreignParticipant = (Classifier)this.classes.get(foreignTableName);
823             if (foreignParticipant == null)
824             {
825                 throw new SchemaTransformerException(
826                     "The associated table '" + foreignTableName +
827                     "' must be available in order to create the association");
828             }
829             foreignEnd.setParticipant(foreignParticipant);
830 
831             if (StringUtils.isNotEmpty(this.columnTaggedValue))
832             {
833                 // add the tagged value for the foreign association end
834                 TaggedValue taggedValue = this.createTaggedValue(corePackage, this.columnTaggedValue, fkColumnName);
835                 if (taggedValue != null)
836                 {
837                     foreignEnd.getTaggedValue().add(taggedValue);
838                 }
839             }
840 
841             association.getConnection().add(foreignEnd);
842             associations.add(association);
843 
844             if (logger.isInfoEnabled())
845             {
846                 logger.info(
847                     "adding association: '" + primaryEnd.getParticipant().getName() + " <--> " +
848                     foreignEnd.getParticipant().getName() + "'");
849             }
850         }
851         DbUtils.closeQuietly(columnRs);
852         return associations;
853     }
854 
855     /***
856      * Creates a tagged value given the specfied <code>name</code>.
857      *
858      * @param name the name of the tagged value to create.
859      * @param value the value to populate on the tagged value.
860      * @return returns the new TaggedValue
861      */
862     protected TaggedValue createTaggedValue(
863         CorePackage corePackage,
864         String name,
865         String value)
866     {
867         Collection values = new HashSet();
868         values.add(value);
869         TaggedValue taggedValue =
870             corePackage.getTaggedValue().createTaggedValue(name, VisibilityKindEnum.VK_PUBLIC, false, values);
871 
872         // see if we can find the tag defintion and if so add that
873         // as the type.
874         Object tagDefinition = ModelElementFinder.find(this.umlPackage, name);
875         if (tagDefinition != null && TagDefinition.class.isAssignableFrom(tagDefinition.getClass()))
876         {
877             taggedValue.setType((TagDefinition)tagDefinition);
878         }
879         return taggedValue;
880     }
881 
882     /***
883      * Gets or creates a stereotypes given the specfied comma seperated list of
884      * <code>names</code>. If any of the stereotypes can't be found, they
885      * will be created.
886      *
887      * @param names comma seperated list of stereotype names
888      * @param baseClass the base class for which the stereotype applies.
889      * @return Collection of Stereotypes
890      */
891     protected Collection getOrCreateStereotypes(
892         CorePackage corePackage,
893         String names,
894         String baseClass)
895     {
896         Collection stereotypes = new HashSet();
897         String[] stereotypeNames = null;
898         if (names != null)
899         {
900             stereotypeNames = names.split(",");
901         }
902         if (stereotypeNames != null && stereotypeNames.length > 0)
903         {
904             for (int ctr = 0; ctr < stereotypeNames.length; ctr++)
905             {
906                 String name = StringUtils.trimToEmpty(stereotypeNames[ctr]);
907 
908                 // see if we can find the stereotype first
909                 Object stereotype = ModelElementFinder.find(this.umlPackage, name);
910                 if (stereotype == null || !Stereotype.class.isAssignableFrom(stereotype.getClass()))
911                 {
912                     Collection baseClasses = new ArrayList();
913                     baseClasses.add(baseClass);
914                     stereotype =
915                         corePackage.getStereotype().createStereotype(
916                             name, VisibilityKindEnum.VK_PUBLIC, false, false, false, false, null, baseClasses);
917                     this.model.getOwnedElement().add(stereotype);
918                 }
919                 stereotypes.add(stereotype);
920             }
921         }
922         return stereotypes;
923     }
924 
925     /***
926      * Adds a foreign key column name to the <code>foreignKeys</code> Map. The
927      * map stores a collection of foreign key names keyed by the given
928      * <code>tableName</code>
929      *
930      * @param tableName the name of the table for which to store the keys.
931      * @param columnName the name of the foreign key column name.
932      */
933     protected void addForeignKey(
934         String tableName,
935         String columnName)
936     {
937         if (StringUtils.isNotBlank(tableName) && StringUtils.isNotBlank(columnName))
938         {
939             Collection foreignKeys = (Collection)this.foreignKeys.get(tableName);
940             if (foreignKeys == null)
941             {
942                 foreignKeys = new HashSet();
943             }
944             foreignKeys.add(columnName);
945             this.foreignKeys.put(tableName, foreignKeys);
946         }
947     }
948 
949     /***
950      * Returns true if the table with the given <code>tableName</code> has a
951      * foreign key with the specified <code>columnName</code>.
952      *
953      * @param tableName the name of the table to check for the foreign key
954      * @param columnName the naem of the foreign key column.
955      * @return true/false dependeing on whether or not the table has the foreign
956      *         key with the given <code>columnName</code>.
957      */
958     protected boolean hasForeignKey(
959         String tableName,
960         String columnName)
961     {
962         boolean hasForeignKey = false;
963         if (StringUtils.isNotBlank(tableName) && StringUtils.isNotBlank(columnName))
964         {
965             Collection foreignKeys = (Collection)this.foreignKeys.get(tableName);
966             if (foreignKeys != null)
967             {
968                 hasForeignKey = foreignKeys.contains(columnName);
969             }
970         }
971         return hasForeignKey;
972     }
973 
974     /***
975      * Creates an attributes multiplicity, if <code>required</code> is true,
976      * then multiplicity is set to 1, if <code>required</code> is false, then
977      * multiplicity is set to 0..1.
978      *
979      * @param dataTypes used to create the Multiplicity
980      * @param required whether or not the attribute is required therefore
981      *        determining the multiplicity value created.
982      * @return the new Multiplicity
983      */
984     protected Multiplicity createAttributeMultiplicity(
985         DataTypesPackage dataTypes,
986         boolean required)
987     {
988         Multiplicity mult = null;
989         if (required)
990         {
991             mult = this.createMultiplicity(dataTypes, 1, 1);
992         }
993         else
994         {
995             mult = this.createMultiplicity(dataTypes, 0, 1);
996         }
997         return mult;
998     }
999 
1000     /***
1001      * Creates a multiplicity, from <code>lower</code> and <code>upper</code>
1002      * ranges.
1003      *
1004      * @param dataTypes used to create the Multiplicity
1005      * @param lower the lower range of the multiplicity
1006      * @param upper the upper range of the multiplicity
1007      * @return the new Multiplicity
1008      */
1009     protected Multiplicity createMultiplicity(
1010         DataTypesPackage dataTypes,
1011         int lower,
1012         int upper)
1013     {
1014         Multiplicity mult = dataTypes.getMultiplicity().createMultiplicity();
1015         MultiplicityRange range = dataTypes.getMultiplicityRange().createMultiplicityRange(lower, upper);
1016         mult.getRange().add(range);
1017         return mult;
1018     }
1019 }