de.erdesignerng.dialect.JDBCReverseEngineeringStrategy.java Source code

Java tutorial

Introduction

Here is the source code for de.erdesignerng.dialect.JDBCReverseEngineeringStrategy.java

Source

/**
 * Mogwai ERDesigner. Copyright (C) 2002 The Mogwai Project.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more *
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package de.erdesignerng.dialect;

import de.erdesignerng.ERDesignerBundle;
import de.erdesignerng.exception.ElementAlreadyExistsException;
import de.erdesignerng.exception.ElementInvalidNameException;
import de.erdesignerng.exception.ReverseEngineeringException;
import de.erdesignerng.model.Attribute;
import de.erdesignerng.model.CascadeType;
import de.erdesignerng.model.CustomType;
import de.erdesignerng.model.Domain;
import de.erdesignerng.model.Index;
import de.erdesignerng.model.IndexExpression;
import de.erdesignerng.model.IndexType;
import de.erdesignerng.model.Model;
import de.erdesignerng.model.ModelProperties;
import de.erdesignerng.model.Relation;
import de.erdesignerng.model.RelationList;
import de.erdesignerng.model.Table;
import de.erdesignerng.model.TableType;
import de.erdesignerng.model.View;
import de.erdesignerng.modificationtracker.VetoException;
import de.erdesignerng.util.SQLUtils;
import de.erdesignerng.visual.MessagesHelper;
import de.erdesignerng.visual.common.ERDesignerWorldConnector;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @param <T> the dialect
 * @author $Author: mirkosertic $
 * @version $Date: 2009-03-13 15:40:33 $
 */
public abstract class JDBCReverseEngineeringStrategy<T extends Dialect> {

    private static final Logger LOGGER = Logger.getLogger(JDBCReverseEngineeringStrategy.class);
    protected final T dialect;

    protected JDBCReverseEngineeringStrategy(T aDialect) {
        dialect = aDialect;
    }

    /**
     * Convert a JDBC Cascade Type to the Mogwai CascadeType.
     * <p/>
     * Default is CASCADE.
     *
     * @param aValue the JDBC type
     * @return the CascadeType
     */
    protected CascadeType getCascadeType(int aValue) {
        switch (aValue) {
        case DatabaseMetaData.importedKeyNoAction:
            return CascadeType.NOTHING;
        case DatabaseMetaData.importedKeySetNull:
            return CascadeType.SETNULL;
        case DatabaseMetaData.importedKeyCascade:
            return CascadeType.CASCADE;
        case DatabaseMetaData.importedKeyRestrict:
            return CascadeType.RESTRICT;
        default:
            return CascadeType.CASCADE;
        }
    }

    protected void reverseEngineerAttribute(Attribute<Table> aAttribute, TableEntry aTable, Connection aConnection)
            throws SQLException {
    }

    protected void reverseEngineerDomain(Model aModel, Domain aDomain, ReverseEngineeringOptions aOptions,
            ReverseEngineeringNotifier aNotifier, Connection aConnection) {
    }

    protected void reverseEngineerCustomType(Model aModel, CustomType aCustomType,
            ReverseEngineeringOptions aOptions, ReverseEngineeringNotifier aNotifier, Connection aConnection) {
        throw new UnsupportedOperationException("Userdefined datatypes (UDTs) not supported for "
                + aModel.getDialect().getUniqueName() + " databases.");
    }

    /**
     * Reverse engineer the sql statement for a view.
     *
     * @param aViewEntry die view entry
     * @param aConnection the connection
     * @param aView   the view
     * @return the sql statement
     * @throws SQLException   is thrown in case of an exception
     */
    protected String reverseEngineerViewSQL(TableEntry aViewEntry, Connection aConnection, View aView)
            throws SQLException {
        return null;
    }

    /**
     * Reverse engineer an existing view.
     *
     * @param aModel   the model
     * @param aOptions   the options
     * @param aNotifier the notifier
     * @param aViewEntry the table
     * @param aConnection the connection
     * @throws SQLException   is thrown in case of an error
     * @throws ReverseEngineeringException is thrown in case of an error
     */
    protected void reverseEngineerView(Model aModel, ReverseEngineeringOptions aOptions,
            ReverseEngineeringNotifier aNotifier, TableEntry aViewEntry, Connection aConnection)
            throws SQLException, ReverseEngineeringException {

        aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGTABLE, aViewEntry.getTableName());

        DatabaseMetaData theMetaData = aConnection.getMetaData();

        String theTablePattern = getEscapedPattern(theMetaData, aViewEntry.getTableName());
        String theSchemaPattern = getEscapedPattern(theMetaData, aViewEntry.getSchemaName());

        ResultSet theViewsResultSet = theMetaData.getTables(aViewEntry.getCatalogName(), theSchemaPattern,
                theTablePattern, new String[] { aViewEntry.getTableType().toString() });

        while (theViewsResultSet.next()) {

            String theViewRemarks = theViewsResultSet.getString("REMARKS");

            View theView = new View();

            theView.setName(dialect.getCastType().cast(aViewEntry.getTableName()));
            theView.setOriginalName(aViewEntry.getTableName());
            switch (aOptions.getTableNaming()) {
            case INCLUDE_SCHEMA:
                theView.setSchema(aViewEntry.getSchemaName());
                break;
            default:
            }

            if (!StringUtils.isEmpty(theViewRemarks)) {
                theView.setComment(theViewRemarks);
            }

            String theStatement = reverseEngineerViewSQL(aViewEntry, aConnection, theView);

            try {
                SQLUtils.updateViewAttributesFromSQL(theView, theStatement);
            } catch (Exception e) {
                LOGGER.warn("View " + theView.getName() + " has a strange SQL : " + theStatement);
            }

            theView.setSql(theStatement);

            // We are done here
            try {
                aModel.addView(theView);
            } catch (VetoException | ElementInvalidNameException | ElementAlreadyExistsException e) {
                throw new ReverseEngineeringException(e.getMessage(), e);
            }

        }

        theViewsResultSet.close();
    }

    protected String getEscapedPattern(DatabaseMetaData aMetaData, String aValue) throws SQLException {
        String thePrefix = aMetaData.getSearchStringEscape();
        if (!StringUtils.isEmpty(thePrefix) && !StringUtils.isEmpty(aValue)) {
            aValue = aValue.replace("_", thePrefix + "_");
            aValue = aValue.replace("%", thePrefix + "%");
        }

        return aValue;
    }

    /**
     * Reverse engineer an existing table.
     *
     * @param aModel     the model
     * @param aOptions     the options
     * @param aNotifier   the notifier
     * @param aTableEntry the table
     * @param aConnection the connection
     * 
     * @return a Map of former ModelProperties of model items in case they have been replaced during reverse engineering into an existing model
     * 
     * @throws SQLException   is thrown in case of an error
     * @throws ReverseEngineeringException is thrown in case of an error
     */
    protected final Map<String, ModelProperties> reverseEngineerTable(Model aModel,
            ReverseEngineeringOptions aOptions, ReverseEngineeringNotifier aNotifier, TableEntry aTableEntry,
            Connection aConnection) throws SQLException, ReverseEngineeringException {
        Map<String, ModelProperties> theExistingModelItemProperties = null;
        aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGTABLE, aTableEntry.getTableName());

        DatabaseMetaData theMetaData = aConnection.getMetaData();

        String theTablePattern = getEscapedPattern(theMetaData, aTableEntry.getTableName());
        String theSchemaPattern = getEscapedPattern(theMetaData, aTableEntry.getSchemaName());

        ResultSet theTablesResultSet = theMetaData.getTables(aTableEntry.getCatalogName(), theSchemaPattern,
                theTablePattern, new String[] { aTableEntry.getTableType().toString() });

        while (theTablesResultSet.next()) {

            String theTableRemarks = theTablesResultSet.getString("REMARKS");

            Table theNewTable = new Table();

            theNewTable.setName(dialect.getCastType().cast(aTableEntry.getTableName()));
            theNewTable.setOriginalName(aTableEntry.getTableName());
            switch (aOptions.getTableNaming()) {
            case INCLUDE_SCHEMA:
                theNewTable.setSchema(aTableEntry.getSchemaName());
                break;
            default:
            }

            if (!StringUtils.isEmpty(theTableRemarks)) {
                theNewTable.setComment(theTableRemarks);
            }

            // Reverse engineer attributes
            ResultSet theColumnsResultSet = theMetaData.getColumns(aTableEntry.getCatalogName(), theSchemaPattern,
                    theTablePattern, null);
            while (theColumnsResultSet.next()) {

                String theColumnName = null;
                String theTypeName = null;
                Integer theSize = null;
                Integer theFraction = null;
                int theRadix = 0;
                int theNullable = 0;
                String theDefaultValue = null;
                String theColumnRemarks = null;

                try {
                    theColumnName = theColumnsResultSet.getString("COLUMN_NAME");
                } catch (SQLException e) {
                }

                try {
                    theTypeName = theColumnsResultSet.getString("TYPE_NAME");
                } catch (SQLException e) {
                }

                try {
                    theSize = theColumnsResultSet.getInt("COLUMN_SIZE");
                } catch (SQLException e) {
                }

                try {
                    theFraction = theColumnsResultSet.getInt("DECIMAL_DIGITS");
                } catch (SQLException e) {
                }

                try {
                    theRadix = theColumnsResultSet.getInt("NUM_PREC_RADIX");
                } catch (SQLException e) {
                }

                try {
                    theNullable = theColumnsResultSet.getInt("NULLABLE");
                } catch (SQLException e) {
                }

                try {
                    theDefaultValue = theColumnsResultSet.getString("COLUMN_DEF");
                    if (!StringUtils.isEmpty(theDefaultValue)) {
                        theDefaultValue = theDefaultValue.trim();
                    }
                } catch (SQLException e) {
                }

                try {
                    theColumnRemarks = theColumnsResultSet.getString("REMARKS");
                } catch (SQLException e) {
                }

                Attribute<Table> theAttribute = new Attribute<>();

                theAttribute.setName(dialect.getCastType().cast(theColumnName));
                if (!StringUtils.isEmpty(theColumnRemarks)) {
                    theAttribute.setComment(theColumnRemarks);
                }

                // Search for the datatype in the domains, the dialect specific and the user defined datatypes
                DataType theDataType = aModel.getAvailableDataTypes()
                        .findByName(dialect.convertTypeNameToRealTypeName(theTypeName));

                if (theDataType == null) {
                    throw new ReverseEngineeringException("Unknown data type " + theTypeName + " for "
                            + aTableEntry.getTableName() + "." + theColumnName);
                }

                boolean isNullable = true;

                switch (theNullable) {
                case DatabaseMetaData.columnNoNulls:
                    isNullable = false;
                    break;
                case DatabaseMetaData.columnNullable:
                    isNullable = true;
                    break;
                default:
                    LOGGER.warn("Unknown nullability : " + theNullable + " for " + theColumnName + " of table "
                            + theNewTable.getName());
                }

                theAttribute.setDatatype(theDataType);
                theAttribute.setSize(theSize);
                theAttribute.setFraction(theFraction);
                theAttribute.setScale(theRadix);
                theAttribute.setDefaultValue(theDefaultValue);
                theAttribute.setNullable(isNullable);

                reverseEngineerAttribute(theAttribute, aTableEntry, aConnection);

                try {
                    theNewTable.addAttribute(aModel, theAttribute);
                } catch (ElementAlreadyExistsException | ElementInvalidNameException e) {
                    throw new ReverseEngineeringException(e.getMessage(), e);
                }
            }
            theColumnsResultSet.close();

            // Reverse engineer primary keys
            reverseEngineerPrimaryKey(aModel, aTableEntry, theMetaData, theNewTable);

            // Reverse engineer indexes
            try {
                reverseEngineerIndexes(aModel, aTableEntry, theMetaData, theNewTable, aNotifier);
            } catch (SQLException e) {
                // if there is an sql exception, just ignore it
            }

            // We are done here
            try {
                aModel.addTable(theNewTable);
            } catch (ElementAlreadyExistsException e1) {
                //this manages the reverse engineering into an existing model and cares only about the table names of the model that conflict with the new table names from the connection
                //TODO: also care about tables that are no longer part of the connection, but still exist in the local model. E.g. show a dialog and ask the user what to do (delete/keep)
                try {
                    //buffer the properties (e.g. position in model) of the existing table and its relations (e.g. the offset of the title) that are going to be replaced
                    Table theExistingTable = aModel.getTables().findByName(theNewTable.getName());
                    RelationList theExistingRelations = aModel.getRelations().getAllRelataionsOf(theExistingTable);

                    if (theExistingModelItemProperties == null) {
                        theExistingModelItemProperties = new HashMap<>();
                    }

                    //store former layouting data for the table and its relations in the old graph
                    theExistingModelItemProperties.put(theExistingTable.getName(),
                            theExistingTable.getProperties());
                    for (Relation anExistingRelation : theExistingRelations) {
                        theExistingModelItemProperties.put(anExistingRelation.getName(),
                                anExistingRelation.getProperties());
                    }

                    //remove old table and its relations
                    aModel.removeTable(theExistingTable);

                    //add the new table without relations
                    aModel.addTable(theNewTable);
                } catch (ElementAlreadyExistsException | ElementInvalidNameException | VetoException e2) {
                    throw new ReverseEngineeringException(e2.getMessage());
                }
            } catch (ElementInvalidNameException | VetoException e3) {
                throw new ReverseEngineeringException(e3.getMessage());
            }
        }

        theTablesResultSet.close();

        return theExistingModelItemProperties;
    }

    protected void reverseEngineerPrimaryKey(Model aModel, TableEntry aTableEntry, DatabaseMetaData aMetaData,
            Table aTable) throws SQLException, ReverseEngineeringException {

        ResultSet thePrimaryKeyResultSet = aMetaData.getPrimaryKeys(aTableEntry.getCatalogName(),
                aTableEntry.getSchemaName(), aTableEntry.getTableName());
        Index thePrimaryKeyIndex = null;

        while (thePrimaryKeyResultSet.next()) {

            String thePKName = thePrimaryKeyResultSet.getString("PK_NAME");
            String theColumnName = thePrimaryKeyResultSet.getString("COLUMN_NAME");

            if (thePrimaryKeyIndex == null) {
                thePrimaryKeyIndex = new Index();
                thePrimaryKeyIndex.setIndexType(IndexType.PRIMARYKEY);
                thePrimaryKeyIndex.setName(convertIndexNameFor(aTable, thePKName));
                thePrimaryKeyIndex.setOriginalName(thePKName);
                if (StringUtils.isEmpty(thePrimaryKeyIndex.getName())) {
                    // Assume the default name is TABLE_NAME+"_PK"
                    thePrimaryKeyIndex.setName(aTableEntry.getTableName() + "_PK");
                }

                try {
                    aTable.addIndex(aModel, thePrimaryKeyIndex);
                } catch (ElementAlreadyExistsException | ElementInvalidNameException e) {
                    throw new ReverseEngineeringException(e.getMessage(), e);
                }
            }

            Attribute<Table> theIndexAttribute = aTable.getAttributes()
                    .findByName(dialect.getCastType().cast(theColumnName));
            if (theIndexAttribute == null) {
                throw new ReverseEngineeringException(
                        "Cannot find attribute " + theColumnName + " in table " + aTable.getName());
            }

            try {
                thePrimaryKeyIndex.getExpressions().addExpressionFor(theIndexAttribute);
            } catch (ElementAlreadyExistsException e) {
                throw new ReverseEngineeringException("Error adding index attribute", e);
            }

        }

        thePrimaryKeyResultSet.close();
    }

    protected String convertIndexNameFor(Table aTable, String aIndexName) {
        return aIndexName;
    }

    protected void reverseEngineerIndexes(Model aModel, TableEntry aTableEntry, DatabaseMetaData aMetaData,
            Table aTable, ReverseEngineeringNotifier aNotifier) throws SQLException, ReverseEngineeringException {

        ResultSet theIndexResults = aMetaData.getIndexInfo(aTableEntry.getCatalogName(),
                aTableEntry.getSchemaName(), aTableEntry.getTableName(), false, true);
        Index theIndex = null;

        while (theIndexResults.next()) {

            String theIndexName = convertIndexNameFor(aTable, theIndexResults.getString("INDEX_NAME"));

            if ((theIndexName != null)
                    && ((theIndex == null) || (!theIndex.getOriginalName().equals(theIndexName)))) {

                String theNewIndexName = dialect.getCastType().cast(theIndexName);

                if (aTable.getIndexes().findByName(theNewIndexName) == null) {
                    theIndex = new Index();
                    theIndex.setName(theNewIndexName);
                    theIndex.setOriginalName(theIndexName);

                    boolean isNonUnique = theIndexResults.getBoolean("NON_UNIQUE");
                    if (isNonUnique) {
                        theIndex.setIndexType(IndexType.NONUNIQUE);
                    } else {
                        theIndex.setIndexType(IndexType.UNIQUE);
                    }

                    aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGINDEX, theIndex.getName());

                    try {
                        aTable.addIndex(aModel, theIndex);
                    } catch (ElementAlreadyExistsException | ElementInvalidNameException e) {
                        throw new ReverseEngineeringException("Cannot add index " + theIndexName + " in table "
                                + aTable.getName() + " : " + e.getMessage(), e);
                    }
                } else {
                    theIndex = null;
                }
            }

            if (theIndex != null) {
                short aPosition = theIndexResults.getShort("ORDINAL_POSITION");

                String theColumnName = theIndexResults.getString("COLUMN_NAME");
                String theASCorDESC = theIndexResults.getString("ASC_OR_DESC");

                reverseEngineerIndexAttribute(aMetaData, aTableEntry, aTable, aNotifier, theIndex, theColumnName,
                        aPosition, theASCorDESC);
            }
        }

        theIndexResults.close();

        // Remove duplicate unique indexes
        Index thePrimaryKey = aTable.getPrimarykey();
        if (thePrimaryKey != null) {
            Set<Index> theDuplicateIndexes = new HashSet<>();
            for (Index theInd : aTable.getIndexes()) {
                if ((theInd.getIndexType() == IndexType.UNIQUE)
                        && (thePrimaryKey.getExpressions().containsAllExpressions(theInd.getExpressions()))) {
                    theDuplicateIndexes.add(theInd);
                }
            }

            aTable.getIndexes().removeAll(theDuplicateIndexes);
        }
    }

    /**
     * Reverse engineer an attribute within an index.
     *
     * @param aMetaData the database meta data
     * @param aTableEntry the current table entry
     * @param aTable   the table
     * @param aNotifier the notifier
     * @param aIndex   the current index
     * @param aColumnName the column name
     * @param aPosition the column position
     * @param aASCorDESC "A" = Ascending, "D" = Descending, NULL = sort not
     * supported
     * @throws SQLException   in case of an error
     * @throws ReverseEngineeringException in case of an error
     */
    protected void reverseEngineerIndexAttribute(DatabaseMetaData aMetaData, TableEntry aTableEntry, Table aTable,
            ReverseEngineeringNotifier aNotifier, Index aIndex, String aColumnName, short aPosition,
            String aASCorDESC) throws SQLException, ReverseEngineeringException {
        Attribute<Table> theIndexAttribute = aTable.getAttributes()
                .findByName(dialect.getCastType().cast(aColumnName));

        if (theIndexAttribute == null) {

            // It seems to be a function based index
            aIndex.getExpressions().addExpressionFor(aColumnName);

        } else {

            // It is a column based index
            try {
                aIndex.getExpressions().addExpressionFor(theIndexAttribute);
            } catch (ElementAlreadyExistsException e) {
                throw new ReverseEngineeringException("Error adding index attribute", e);
            }
        }
    }

    /**
     * Reverse engineer relations for a table.
     *
     * @param aModel   the model
     * @param aOptions   the options
     * @param aNotifier the notifier
     * @param aTableEntry the table entry
     * @param aConnection the connection
     * @throws SQLException   is thrown in case of an error
     * @throws ReverseEngineeringException is thrown in case of an error
     */
    protected void reverseEngineerRelations(Model aModel, ReverseEngineeringOptions aOptions,
            ReverseEngineeringNotifier aNotifier, TableEntry aTableEntry, Connection aConnection)
            throws SQLException, ReverseEngineeringException {

        DatabaseMetaData theMetaData = aConnection.getMetaData();

        String theSchemaName = null;
        String theCatalogName = null;
        if (aTableEntry != null) {
            theSchemaName = aTableEntry.getSchemaName();
            theCatalogName = aTableEntry.getCatalogName();
        }

        int theSysCounter = 0;

        List<Relation> theNewRelations = new ArrayList<>();

        String theImportingTableName = aModel.getDialect().getCastType().cast(aTableEntry.getTableName());
        aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGRELATION, theImportingTableName);

        Table theImportingTable;

        switch (aOptions.getTableNaming()) {
        case STANDARD:
            theImportingTable = aModel.getTables().findByName(theImportingTableName);
            break;
        case INCLUDE_SCHEMA:
            theImportingTable = aModel.getTables().findByNameAndSchema(theImportingTableName, theSchemaName);
            break;
        default:
            throw new RuntimeException("Not supported naming type");
        }
        if (theImportingTable == null) {
            throw new ReverseEngineeringException("Cannot find table in model : " + theImportingTableName);
        }

        String theOldFKName = null;

        // Foreign keys
        Relation theNewRelation = null;
        ResultSet theForeignKeys = theMetaData.getImportedKeys(theCatalogName, theSchemaName,
                aTableEntry.getTableName());

        while (theForeignKeys.next()) {
            String theFKName = theForeignKeys.getString("FK_NAME");

            if ((theNewRelation == null) || (!theFKName.equals(theOldFKName))) {

                theOldFKName = theFKName;

                String thePKTableName = theForeignKeys.getString("PKTABLE_NAME");
                String thePKTableSchema = theForeignKeys.getString("PKTABLE_SCHEM");

                String theUpdateRule = theForeignKeys.getString("UPDATE_RULE");
                String theDeleteRule = theForeignKeys.getString("DELETE_RULE");

                Table theExportingTable;
                switch (aOptions.getTableNaming()) {
                case INCLUDE_SCHEMA:
                    theExportingTable = aModel.getTables()
                            .findByNameAndSchema(dialect.getCastType().cast(thePKTableName), thePKTableSchema);
                    break;

                case STANDARD:
                    theExportingTable = aModel.getTables().findByName(dialect.getCastType().cast(thePKTableName));
                    break;

                default:
                    throw new RuntimeException("Naming not supported : " + aOptions.getTableNaming());
                }

                if (theExportingTable != null) {

                    // The relation is only added to the model
                    // if the exporting table is also part of the model
                    String theRelationName = dialect.getCastType().cast(theFKName);
                    theNewRelation = aModel.getRelations().findByName(theRelationName);

                    boolean addNew = false;
                    if (theNewRelation == null) {
                        addNew = true;
                    } else {
                        if (!theNewRelation.getExportingTable().equals(theExportingTable)
                                || !theNewRelation.getImportingTable().equals(theImportingTable)) {
                            theRelationName = "ERRELSYS_" + theSysCounter++;
                            addNew = true;
                        }
                    }

                    if (addNew) {

                        theNewRelation = new Relation();
                        theNewRelation.setName(dialect.getCastType().cast(theRelationName));
                        theNewRelation.setOriginalName(theRelationName);

                        theNewRelation.setExportingTable(theExportingTable);
                        theNewRelation.setImportingTable(theImportingTable);

                        if (theUpdateRule != null) {
                            int theType = Integer.parseInt(theUpdateRule);

                            theNewRelation.setOnUpdate(getCascadeType(theType));
                        } else {

                            theNewRelation.setOnUpdate(CascadeType.NOTHING);
                        }

                        if (theDeleteRule != null) {
                            int theType = Integer.parseInt(theDeleteRule);

                            theNewRelation.setOnDelete(getCascadeType(theType));
                        } else {
                            theNewRelation.setOnDelete(CascadeType.NOTHING);
                        }

                        theNewRelations.add(theNewRelation);
                    }
                }
            }

            if ((theNewRelation != null) && (theNewRelation.getImportingTable() != null)
                    && (theNewRelation.getExportingTable() != null)) {
                String thePKColumnName = dialect.getCastType().cast(theForeignKeys.getString("PKCOLUMN_NAME"));
                String theFKColumnName = dialect.getCastType().cast(theForeignKeys.getString("FKCOLUMN_NAME"));

                Attribute<Table> theExportingAttribute = theNewRelation.getExportingTable().getAttributes()
                        .findByName(dialect.getCastType().cast(thePKColumnName));
                if (theExportingAttribute == null) {
                    throw new ReverseEngineeringException("Cannot find column " + thePKColumnName + " in table "
                            + theNewRelation.getExportingTable().getName());
                }

                Index thePrimaryKey = theNewRelation.getExportingTable().getPrimarykey();
                if (thePrimaryKey == null) {
                    throw new ReverseEngineeringException("Table " + theNewRelation.getExportingTable().getName()
                            + " does not have a primary key");
                }

                IndexExpression theExpression = thePrimaryKey.getExpressions().findByAttributeName(thePKColumnName);
                if (theExpression == null) {
                    throw new RuntimeException("Cannot find attribute " + thePKColumnName
                            + " in primary key for table " + theNewRelation.getExportingTable().getName());
                }

                Attribute<Table> theImportingAttribute = theNewRelation.getImportingTable().getAttributes()
                        .findByName(theFKColumnName);
                if (theImportingAttribute == null) {
                    throw new ReverseEngineeringException("Cannot find column " + theFKColumnName + " in table "
                            + theNewRelation.getImportingTable().getName());
                }

                theNewRelation.getMapping().put(theExpression, theImportingAttribute);
            }
        }

        theForeignKeys.close();

        try {
            for (Relation theRelation : theNewRelations) {
                try {
                    aModel.addRelation(theRelation);
                } catch (ElementAlreadyExistsException e) {
                    // This might happen for instance on DB2 databases. We will try to generate a new name here!!!
                    int counter = 0;
                    String theNewName = null;
                    while (counter == 0
                            || aModel.getRelations().findByName(dialect.getCastType().cast(theNewName)) != null) {
                        counter++;
                        theNewName = theRelation.getExportingTable().getName() + "_"
                                + theRelation.getImportingTable() + "_FK" + counter;
                    }

                    LOGGER.warn("Relation " + theRelation.getName() + " exists. Renaming it to " + theNewName);
                    theRelation.setName(theNewName);
                    aModel.addRelation(theRelation);
                }
            }
        } catch (ElementInvalidNameException | VetoException | ElementAlreadyExistsException e) {
            throw new ReverseEngineeringException(e.getMessage(), e);
        }
    }

    /**
     * Check if the table is a valid table for reverse engineering.
     *
     * @param aTableName the table name
     * @return true if the table is valid, else false
     */
    protected boolean isValidTable(String aTableName) {
        return true;
    }

    /**
     * Check if the table is a valid view for reverse engineering.
     *
     * @param aTableName the table name
     * @return true if the table is valid, else false
     */
    protected boolean isValidView(String aTableName) {
        return true;
    }

    public void updateModelFromConnection(Model aModel, ERDesignerWorldConnector aConnector, Connection aConnection,
            ReverseEngineeringOptions aOptions, ReverseEngineeringNotifier aNotifier)
            throws SQLException, ReverseEngineeringException {
        Map<String, ModelProperties> theGlobalPreviousModelItemProperties = new HashMap<>();
        Exception theUDTError = null;

        if (aModel.getDialect().isSupportsCustomTypes()) {
            try {
                reverseEngineerCustomTypes(aModel, aOptions, aNotifier, aConnection);
            } catch (ReverseEngineeringException e) {
                theUDTError = e;
            }
        }

        if (aModel.getDialect().isSupportsDomains()) {
            reverseEngineerDomains(aModel, aOptions, aNotifier, aConnection);
        }

        for (TableEntry theTable : aOptions.getTableEntries()) {
            if (TableType.VIEW.equals(theTable.getTableType())) {
                reverseEngineerView(aModel, aOptions, aNotifier, theTable, aConnection);
            } else {
                Map<String, ModelProperties> theLocalPreviousModelItemProperties = reverseEngineerTable(aModel,
                        aOptions, aNotifier, theTable, aConnection);
                if (theLocalPreviousModelItemProperties != null) {
                    theGlobalPreviousModelItemProperties.putAll(theLocalPreviousModelItemProperties);
                }
            }
        }

        for (TableEntry theTableEntry : aOptions.getTableEntries()) {
            // Reverse engineer only relations for tables, not for views!
            if (TableType.TABLE.equals(theTableEntry.getTableType())) {
                reverseEngineerRelations(aModel, aOptions, aNotifier, theTableEntry, aConnection);
            }
        }

        //transfer the properties of formerly removed tables/relations to the newly added tables/relations in case their types and names match
        for (Map.Entry pairs : theGlobalPreviousModelItemProperties.entrySet()) {
            String modelItemName = (String) pairs.getKey();
            ModelProperties modelItemProperties = (ModelProperties) pairs.getValue();

            Table theNewTable = aModel.getTables().findByName(modelItemName);
            if (theNewTable != null) {
                theNewTable.setProperties(modelItemProperties);
            } else {
                Relation theNewRelation = aModel.getRelations().findByName(modelItemName);
                if (theNewRelation != null) {
                    theNewRelation.setProperties(modelItemProperties);
                }
            }
        }

        aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGFINISHED, "");

        if (theUDTError != null) {
            MessagesHelper.displayInfoMessage(null, theUDTError.getMessage());
        }
    }

    protected void reverseEngineerCustomTypes(Model aModel, ReverseEngineeringOptions aOptions,
            ReverseEngineeringNotifier aNotifier, Connection aConnection)
            throws SQLException, ReverseEngineeringException {
        throw new UnsupportedOperationException("Userdefined datatypes (UDTs) not supported for "
                + aModel.getDialect().getUniqueName() + " databases.");
    }

    /**
     * Reverse engineer the domains.
     *
     * @param aModel   - model
     * @param aOptions   - options
     * @param aNotifier - notifier
     * @param aConnection - connection
     * @throws SQLException   - SQL exception
     * @throws ReverseEngineeringException - reverse engineering exception
     */
    protected void reverseEngineerDomains(Model aModel, ReverseEngineeringOptions aOptions,
            ReverseEngineeringNotifier aNotifier, Connection aConnection)
            throws SQLException, ReverseEngineeringException {
        PreparedStatement theStatement = aConnection
                .prepareStatement("SELECT * FROM information_schema.domains where DOMAIN_SCHEMA = ?");

        for (SchemaEntry theEntry : aOptions.getSchemaEntries()) {
            theStatement.setString(1, theEntry.getSchemaName());
            ResultSet theResult = theStatement.executeQuery();

            while (theResult.next()) {
                String theDomainName = theResult.getString("DOMAIN_NAME");
                String theDataType = theResult.getString("DATA_TYPE");
                Integer theSize = null;
                try {
                    Integer theTemp = theResult.getInt("NUMERIC_PRECISION");
                    if (theTemp != null) {
                        theSize = theTemp;
                    }
                } catch (SQLException e) {
                }
                try {
                    Integer theTemp = theResult.getInt("CHARACTER_MAXIMUM_LENGTH");
                    if (theTemp != null) {
                        theSize = theTemp;
                    }
                } catch (SQLException e) {
                }

                int theFraction = theResult.getInt("NUMERIC_PRECISION_RADIX");
                int theScale = theResult.getInt("NUMERIC_SCALE");

                aNotifier.notifyMessage(ERDesignerBundle.ENGINEERINGDOMAIN, theDomainName);

                Domain theDomain = aModel.getDomains().findByName(theDomainName);
                if (theDomain != null) {
                    throw new ReverseEngineeringException("Duplicate domain found : " + theDomainName);
                }

                DataType theType = aModel.getDialect().getDataTypes()
                        .findByName(dialect.convertTypeNameToRealTypeName(theDataType));
                if (theType != null) {
                    theDomain = new Domain();
                    theDomain.setName(theDomainName);
                    theDomain.setConcreteType(theType);
                    theDomain.setSize(theSize);
                    theDomain.setFraction(theFraction);
                    theDomain.setScale(theScale);

                    try {
                        aModel.addDomain(theDomain);
                    } catch (VetoException e) {
                        throw new ReverseEngineeringException(e.getMessage(), e);
                    }
                } else {
                    throw new ReverseEngineeringException(
                            "Unknown data type " + theDataType + " for domain " + theDomainName);
                }

                reverseEngineerDomain(aModel, theDomain, aOptions, aNotifier, aConnection);
            }

            theResult.close();
        }

        theStatement.close();
    }

    public List<SchemaEntry> getSchemaEntries(Connection aConnection) throws SQLException {
        List<SchemaEntry> theList = new ArrayList<>();

        DatabaseMetaData theMetadata = aConnection.getMetaData();
        ResultSet theResult = theMetadata.getSchemas();

        while (theResult.next()) {
            String theSchemaName = theResult.getString("TABLE_SCHEM");
            String theCatalogName = theResult.getString("TABLE_CATALOG");

            theList.add(new SchemaEntry(theCatalogName, theSchemaName));
        }

        return theList;
    }

    protected List<TableEntry> getTablesForSchemaEntry(Connection aConnection, SchemaEntry aEntry)
            throws SQLException {
        List<TableEntry> theResult = new ArrayList<>();

        DatabaseMetaData theMetaData = aConnection.getMetaData();

        // Reverse engineer tables
        ResultSet theTablesResultSet;
        String theCatalogName = null;
        String theSchemaName = null;
        if (aEntry != null) {
            theCatalogName = aEntry.getCatalogName();
            theSchemaName = aEntry.getSchemaName();
            theTablesResultSet = theMetaData.getTables(theCatalogName, theSchemaName, null, TableType.toArray());
        } else {
            theTablesResultSet = theMetaData.getTables(null, null, null, TableType.toArray());
        }

        while (theTablesResultSet.next()) {

            TableType theTableType = TableType.fromString(theTablesResultSet.getString("TABLE_TYPE"));
            String theTableName = theTablesResultSet.getString("TABLE_NAME");

            if (TableType.VIEW.equals(theTableType)) {
                if (isValidView(theTableName)) {
                    TableEntry theEntry = new TableEntry(theCatalogName, theSchemaName, theTableName, theTableType);
                    theResult.add(theEntry);
                }
            } else {
                if (isValidTable(theTableName)) {
                    TableEntry theEntry = new TableEntry(theCatalogName, theSchemaName, theTableName, theTableType);
                    theResult.add(theEntry);
                }
            }
        }

        theTablesResultSet.close();

        return theResult;
    }

    public List<TableEntry> getTablesForSchemas(Connection aConnection, List<SchemaEntry> aSchemaEntries)
            throws SQLException {
        List<TableEntry> theResult = new ArrayList<>();

        if (dialect.isSupportsSchemaInformation()) {
            for (SchemaEntry theEntry : aSchemaEntries) {
                theResult.addAll(getTablesForSchemaEntry(aConnection, theEntry));
            }
        } else {
            theResult.addAll(getTablesForSchemaEntry(aConnection, null));
        }

        return theResult;
    }

    protected String extractSelectDDLFromViewDefinition(String theViewDefinition) {
        if (!StringUtils.isEmpty(theViewDefinition)) {

            theViewDefinition = theViewDefinition.replace('\n', ' ');
            theViewDefinition = theViewDefinition.replace('\r', ' ');
            theViewDefinition = theViewDefinition.replace('\t', ' ');
            theViewDefinition = theViewDefinition.replace('\t', ' ').trim();

            String theViewDefinitionLower = theViewDefinition.toLowerCase();
            theViewDefinitionLower = theViewDefinitionLower.trim();

            if (theViewDefinitionLower.startsWith("create")) {
                int p = theViewDefinitionLower.indexOf(" as ");
                if (p >= 0) {
                    theViewDefinition = theViewDefinition.substring(p + 4).trim();
                }
            }

            if (theViewDefinition.endsWith(";")) {
                theViewDefinition = theViewDefinition.substring(0, theViewDefinition.length() - 1);
            }
        }

        return theViewDefinition;
    }
}