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 private String packageName = null/package-summary.html">ong> 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 public void setPackageName(String packageName)/package-summary.html">ong> 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
365
366 this.model = (Model)models.iterator().next();
367 }
368 else
369 {
370 this.model = modelManagementPackage.getModel().createModel();
371 }
372
373
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
446
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
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
493 modelPackage.getOwnedElement().addAll(this.createAssociations(metadata, corePackage, tableName));
494
495
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
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
567
568 if (!this.hasForeignKey(tableName, columnName))
569 {
570 Classifier typeClass = null;
571
572
573
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
585
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
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
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
750 String fkColumnName = columnRs.getString("FKCOLUMN_NAME");
751 this.addForeignKey(tableName, fkColumnName);
752
753
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
760
761
762
763 int primaryUpper = -1;
764 if (primaryKeys.contains(fkColumnName))
765 {
766 primaryUpper = 1;
767 }
768
769 String endName = null;
770
771
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
800
801 AggregationKindEnum foreignAggregation = AggregationKindEnum.AK_NONE;
802 if (deleteRule == DatabaseMetaData.importedKeyCascade)
803 {
804 foreignAggregation = AggregationKindEnum.AK_COMPOSITE;
805 }
806
807
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
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
873
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
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 }