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