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.ResultSetMetaData;
8   import java.sql.SQLException;
9   import java.util.ArrayList;
10  import java.util.Collection;
11  import java.util.HashMap;
12  import java.util.HashSet;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.StringTokenizer;
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   * Performs the transformation of database schema to XMI.
49   *
50   * TODO This class really should have the functionality it uses (writing model
51   *       elements) moved to the metafacades.
52   * TODO This class should be refactored into smaller classes.
53   * @author Chad Brandon
54   * @author Joel Kozikowski
55   */
56  public class SchemaTransformer
57  {
58      private static final 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      private 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<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 }