All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.kernelpanic.dbmonster.SchemaGrabber Maven / Gradle / Ivy

/* Version 1.0 based on Apache Software License 1.1
 *
 * Copyright (c) 2003 Piotr Maj and DBMonster developers. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The end-user documentation included with the redistribution, if any,
 *    must include the following acknowledgment:
 *
 *    "This product includes software developed by DBMonster developers
 *    (http://dbmonster.kernelpanic.pl/)."
 *
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The name "DBMonster" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "DBMonster",
 *    nor may "DBMonster" appear in their name, without prior written
 *    permission of Piotr Maj.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE DBMONSTER DEVELOPERS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package pl.kernelpanic.dbmonster;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import pl.kernelpanic.dbmonster.connection.ConnectionProvider;
import pl.kernelpanic.dbmonster.connection.DBCPConnectionProvider;
import pl.kernelpanic.dbmonster.connection.Transaction;
import pl.kernelpanic.dbmonster.generator.BinaryGenerator;
import pl.kernelpanic.dbmonster.generator.BooleanGenerator;
import pl.kernelpanic.dbmonster.generator.DataGenerator;
import pl.kernelpanic.dbmonster.generator.DateTimeGenerator;
import pl.kernelpanic.dbmonster.generator.ForeignKeyGenerator;
import pl.kernelpanic.dbmonster.generator.KeyGenerator;
import pl.kernelpanic.dbmonster.generator.MaxKeyGenerator;
import pl.kernelpanic.dbmonster.generator.NullGenerator;
import pl.kernelpanic.dbmonster.generator.NumberGenerator;
import pl.kernelpanic.dbmonster.generator.StringGenerator;
import pl.kernelpanic.dbmonster.generator.StringKeyGenerator;
import pl.kernelpanic.dbmonster.schema.Column;
import pl.kernelpanic.dbmonster.schema.Key;
import pl.kernelpanic.dbmonster.schema.Schema;
import pl.kernelpanic.dbmonster.schema.SchemaException;
import pl.kernelpanic.dbmonster.schema.SchemaUtil;
import pl.kernelpanic.dbmonster.schema.Table;
import pl.kernelpanic.dbmonster.sql.ExtendedTypes;

/**
 * Schema Grabber.
 *
 * @author Piotr Maj <[email protected]>
 *
 * @version $Id: SchemaGrabber.java,v 1.3 2006/01/05 16:29:37 majek Exp $
 */
public class SchemaGrabber {

    /**
     * Logger.
     */
    private Log log = LogFactory.getLog(SchemaGrabber.class);

    /**
     * Connection provider.
     */
    private ConnectionProvider connectionProvider = null;

    /**
     * Specifies the tables we want to grab. If set to null
     * all tables will be grabbed.
     */
    private Vector tables = null;

    /**
     * Output stream.
     */
    private OutputStream output = System.out;

    /**
     * Properties.
     */
    private Properties properties = new Properties();

    /**
     * Database schema used in grabbing table. Useful for Oracle,
     * and other databases that support schema.
     */
    private String dbSchema = null;

    /**
     * Number of rows to generate.
     */
    private int numRows = 1000;

    /**
     * Constructs new SchemaGrabber.
     */
    public SchemaGrabber() {
    }

    /**
     * Starts SchemaGrabber
     *
     * @param args command line arguments
     *
     * @throws Exception on errors
     */
    public static void main(String[] args) throws Exception {
        SchemaGrabber sg = new SchemaGrabber();
        ConnectionProvider cp = new DBCPConnectionProvider();
        sg.setConnectionProvider(cp);
        Schema schema = sg.grabSchema();
        OutputStream os = sg.getOutput();
        SchemaUtil.serializeSchema(new PrintWriter(os), schema);
    }

    /**
     * Starts schema grabber.
     *
     * @throws Exception on errors
     */
    public void doTheJob() throws Exception {
        Schema schema = grabSchema();
        SchemaUtil.serializeSchema(new PrintWriter(output), schema);
    }

    /**
     * Returns the output stream where the serialized schema should
     * be pushed.
     *
     * @return output stream
     */
    public OutputStream getOutput() {
        return output;
    }

    /**
     * Grabs the schema.
     *
     * @return Schema schema
     *
     * @throws Exception on errors
     */
    public Schema grabSchema() throws Exception {

        // read needed properties first.
        dbSchema = properties.getProperty("dbmonster.jdbc.schema", null);
        if (dbSchema != null && "".equals(dbSchema)) {
            dbSchema = null;
        }
        String rows = properties.getProperty("dbmonster.rows", "1000");
        try {
            numRows = Integer.valueOf(rows).intValue();
        } catch (Exception e) {
        }
        Schema schema = new Schema();
        schema.setName("Change me!");
        if (tables == null) {
            tables = getTableNames();
        }
        Iterator it = tables.iterator();
        int count = tables.size();
        int current = 1;
        log.info("Grabbing schema from database. "
            + count + " tables to grab.");
        while (it.hasNext()) {
            String tableName = (String) it.next();
            Table t = grabTable(tableName);
            schema.addTable(t);
            log.info("Grabbing table " + tableName + ". "
                 + ((current * 100) / count) + "% done.");
            ++current;
        }
        log.info("Grabbing schema from database complete.");
        return schema;
    }

    /**
     * Grabs the table.
     *
     * @param name table name
     *
     * @return table
     *
     * @throws SQLException on SQL errors
     * @throws SchemaException on schema errors
     */
    public Table grabTable(String name)
        throws SQLException, SchemaException {
        ResultSet rs;

        Table t = new Table();
        String tableName = name;
        if (dbSchema != null) {
            tableName = dbSchema + "." + tableName;
        }
        t.setName(tableName);
        t.setRows(numRows);
        Transaction tx = null;
        try {
            tx = new Transaction(connectionProvider);
            Connection conn = tx.begin();
            DatabaseMetaData md = conn.getMetaData();

            // key
            Vector keyColumns = new Vector();
            rs = md.getPrimaryKeys(null, dbSchema, name);
            while (rs.next()) {
                String columnName = rs.getString("COLUMN_NAME");
                keyColumns.add(columnName);
            }
            KeyGenerator generator = suggestKeyGenerator(name, keyColumns);
            if (generator != null) {
                Key key = new Key();
                key.setGenerator(generator);
                t.setKey(key);
            }
            else {
                // This is to import anyway multiple keys.
                keyColumns.clear();
            }

            //imported keys
            HashMap foreignKeys = new HashMap();
            rs = md.getImportedKeys(null, null, name);
            while (rs.next()) {
                String fkColumn = rs.getString("FKCOLUMN_NAME");
                String pkColumn = rs.getString("PKCOLUMN_NAME");
                String pkTable = rs.getString("PKTABLE_NAME");
/*                String pkSchema = rs.getString("PKTABLE_SCHEM");
// Removed! Tables for ForeignKeyGenerator are not found if these lines
// are active!
                if (pkSchema != null) {
                    pkTable = pkSchema + "." + pkTable;
                }*/
                foreignKeys.put(fkColumn, new PairBean(pkTable, pkColumn));
            }

            // columns
            rs = md.getColumns(null, dbSchema, name, "%");
            while (rs.next()) {
                String columnName = rs.getString("COLUMN_NAME");
                if (keyColumns.contains(columnName)) {
                    continue;
                }
                Column c = new Column();
                c.setName(columnName);
                c.setGenerator(suggestGenerator(name, columnName, foreignKeys));
                t.addColumn(c);
            }

        } catch (SQLException e) {
            tx.abort();
            log.fatal(e.getMessage());
            throw e;
        } finally {
            if (tx != null) {
                tx.close();
            }
        }
        return t;
    }

    /**
     * Returns names of the tables in the database.
     *
     * @return vector of strings
     *
     * @throws SQLException on errors
     */
    public Vector getTableNames() throws SQLException {
        Vector v = new Vector();
        Transaction tx = null;
        try {
            tx = new Transaction(connectionProvider);
            Connection conn = tx.begin();
            DatabaseMetaData md = conn.getMetaData();
            ResultSet rs =
                md.getTables(null, dbSchema, "%", new String[] {"TABLE"});
            while (rs.next()) {
                String name = rs.getString("TABLE_NAME");
                v.add(name);
            }
            tx.commit();
        } catch (SQLException e) {
            log.fatal(e.getMessage());
            tx.abort();
            throw e;
        } finally {
            if (tx != null) {
                tx.close();
            }
        }
        return v;
    }

    /**
     * Returns a connection provider used by the grabber.
     *
     * @return connection provider instance
     */
    public ConnectionProvider getConnectionProvider() {
        return connectionProvider;
    }

    /**
     * Sets the connection provider.
     *
     * @param provider connection provider
     */
    public void setConnectionProvider(ConnectionProvider provider) {
        connectionProvider = provider;
    }

    /**
     * Returns the logger.
     *
     * @return logger
     */
    public Log getLog() {
        return log;
    }

    /**
     * Sets the logger
     *
     * @param logger new logger
     */
    public void setLog(Log logger) {
        log = logger;
    }

    /**
     * Adds a table names to the list of grabbed tables.
     *
     * @param name name of the table
     *
     * @throws SQLException if table cannot be found
     */
    public void addTable(String name) throws SQLException {
        if (tables == null) {
            tables = new Vector();
        }
        String tableName = name;
        Transaction tx = null;
        try {
            tx = new Transaction(connectionProvider);
            Connection conn = tx.begin();
            DatabaseMetaData md = conn.getMetaData();
            if (md.storesLowerCaseIdentifiers()) {
                tableName = name.toLowerCase();
            } else if (md.storesUpperCaseIdentifiers()) {
                tableName = name.toUpperCase();
            }
            ResultSet rs =
                md.getTables(null, dbSchema, tableName, new String[] {"TABLE"});
            if (!rs.next()) {
                throw new SQLException("No such table <" + name + ">.");
            }
            tx.commit();
            tables.add(tableName);
        } catch (SQLException e) {
            log.fatal(e.getMessage());
            tx.abort();
            throw e;
        } finally {
            if (tx != null) {
                tx.close();
            }
        }
    }

    /**
     * Sets the output file name.
     *
     * @param file file name
     *
     * @throws IOException on I/O errors
     */
    public void setOutputFile(String file) throws IOException {
        File f = new File(file);
        output = new FileOutputStream(f);
    }

    /**
     * Sets the properties for the SchemaGrabber
     * @param p
     */
    public void setProperties(Properties p) {
        properties = p;
    }

    /**
     * Suggests which key generator should be used. In current implementation
     * it only check if the key consists of only one table and if so it uses
     * MaxKeyGenerator (it the type of the column allow it).
     *
     * @param tableName the name ot the table
     * @param columns columns
     *
     * @return key generator or null
     *
     * @throws SQLException on errors
     */
    private KeyGenerator suggestKeyGenerator(String tableName, Vector columns) throws SQLException {

        KeyGenerator generator = null;

        if (columns.size() != 1) {
            return generator;
        }
        String columnName = (String) columns.get(0);
        Transaction tx = null;
        try {
            tx = new Transaction(connectionProvider);
            Connection conn = tx.begin();
            DatabaseMetaData md = conn.getMetaData();
            ResultSet rs = md.getColumns(null, null, tableName, columnName);
            rs.next();
            int dataType = rs.getInt("DATA_TYPE");
            if (isIntegerType(dataType)) {
                generator = new MaxKeyGenerator();
                ((MaxKeyGenerator) generator).setColumnName(columnName);
            } else if (isTextType(dataType)) {
                generator = new StringKeyGenerator();
                ((StringKeyGenerator) generator).setStartValue("0");
                ((StringKeyGenerator) generator).setColumnName(columnName);
            } else {
                if (log.isWarnEnabled()) {
                    log.warn("Datatype " + dataType + " for " + columnName
                        + " is unknown, no Key Generator in schema.xml");
                }
            }
            tx.commit();
        } catch (SQLException e) {
            tx.abort();
            log.fatal(e.getMessage());
            throw e;
        } finally {
            if (tx != null) {
                tx.close();
            }
        }
        return generator;
    }

    /**
     * Suggests the data generator for a column.
     *
     * @param tableName table name
     * @param columnName column name
     * @param foreignKeys infomation about foreing keys in this table
     *
     * @return data generator. Never null.
     *
     * @throws SQLException on SQL errors
     */
    private DataGenerator suggestGenerator(
        String tableName,
        String columnName,
        Map foreignKeys) throws SQLException {

        DataGenerator generator = new NullGenerator();
        if (foreignKeys.containsKey(columnName)) {
            generator = new ForeignKeyGenerator();
            PairBean bean = (PairBean) foreignKeys.get(columnName);
            ((ForeignKeyGenerator) generator).setTableName(bean.getKey());
            ((ForeignKeyGenerator) generator).setColumnName(bean.getValue());
            return generator;
        }
        Transaction tx = null;
        try {
            tx = new Transaction(connectionProvider);
            Connection conn = tx.begin();
            DatabaseMetaData md = conn.getMetaData();
            ResultSet rs = md.getColumns(null, null, tableName, columnName);
            rs.next();
            int dataType = rs.getInt("DATA_TYPE");
            String isNullable = rs.getString("IS_NULLABLE");
            int nulls = 0;
            if (isNullable == null) {
                isNullable = "NO";
            }
            if ("YES".equals(isNullable)) {
                nulls = 10;
            }
            int columnSize = rs.getInt("COLUMN_SIZE");
            int decimalDigits = rs.getInt("DECIMAL_DIGITS");
            if (isIntegerType(dataType)) {
                generator = new NumberGenerator();
                NumberGenerator numGen = (NumberGenerator) generator;
                numGen.setNulls(nulls);
                if (dataType == Types.BIGINT) {
                    numGen.setReturnedType("long");
                } else if (dataType == Types.INTEGER) {
                    numGen.setReturnedType("integer");
                } else if (dataType == Types.SMALLINT
                    || dataType == Types.TINYINT) {
                    numGen.setReturnedType("short");
                } else if (dataType == Types.DECIMAL || dataType == Types.NUMERIC) {
                    if (decimalDigits > 0) {
                        numGen.setReturnedType("float");
                        numGen.setScale(decimalDigits);
                    } else {
                        numGen.setReturnedType("integer");
                    }
                }
            } else if (isDoubleType(dataType)) {
                generator = new NumberGenerator();
                NumberGenerator numGen = (NumberGenerator) generator;
                numGen.setNulls(nulls);
                if (decimalDigits > 0) {
                    numGen.setReturnedType("double");
                    numGen.setScale(decimalDigits);
                } else {
                    numGen.setReturnedType("long");
                }
            } else if (isBooleanType(dataType)) {
                generator = new BooleanGenerator();
                ((BooleanGenerator) generator).setNulls(nulls);
            } else if (isTextType(dataType)) {
                generator = new StringGenerator();
                ((StringGenerator) generator).setNulls(nulls);
                if (columnSize > 0) {
                    ((StringGenerator) generator).setMaxLength(columnSize);
                }
            } else if (isTimeType(dataType)) {
                generator = new DateTimeGenerator();
                ((DateTimeGenerator) generator).setNulls(nulls);
                if (dataType == Types.DATE) {
                    ((DateTimeGenerator) generator).setReturnedType("date");
                } else if (dataType == Types.TIME) {
                    ((DateTimeGenerator) generator).setReturnedType("time");
                }  else if (dataType == Types.TIMESTAMP) {
                    ((DateTimeGenerator) generator)
                        .setReturnedType("timestamp");
                }
            } else if (isBinaryType(dataType)) {
                generator = new BinaryGenerator();
                ((BinaryGenerator) generator).setNulls(nulls);
                if (columnSize > 0) {
                    ((BinaryGenerator) generator).setMaxLength(columnSize);
                }                
            } else {
                if (log.isWarnEnabled()) {
                    log.warn("Unknown datatype. No generator created.");
                }
            }
            tx.commit();
        } finally {
            if (tx != null) {
                tx.close();
            }
        }
        return generator;
    }

    /**
     * Checks if the type is one of numeric type.
     *
     * @param type type to check
     *
     * @return true if type accepts numbers
     */
    private boolean isIntegerType(int type) {
        int tempType;
        
        tempType = ExtendedTypes.getInstance().
            getStandardAlias(properties.getProperty(
            "dbmonster.jdbc.driver"), type);
        
        return (tempType == Types.BIGINT)
            || (tempType == Types.INTEGER)
            || (tempType == Types.SMALLINT)
            || (tempType == Types.TINYINT)
            || (tempType == Types.DECIMAL)
            || (tempType == Types.NUMERIC);
    }

    /**
     * Checks if the type is one of double type.
     * 
     * @param type type to check
     * 
     * @return true if type accepts numbers; 
     *         false otherwise
     */
    private boolean isDoubleType(int type) {
        return type == Types.DOUBLE;
    }
    
    /**
     * Checks if the type is one of text or character type.
     *
     * @param type type to check
     *
     * @return true if type accepts strings
     */
    private boolean isTextType(int type) {
        int tempType;
        
        tempType = ExtendedTypes.getInstance().
            getStandardAlias(properties.getProperty(
            "dbmonster.jdbc.driver"), type);
        
        return (tempType == Types.CHAR)
            || (tempType == Types.LONGVARCHAR)
            || (tempType == Types.VARCHAR);
    }
    /**
     * Checks if the type is one of text or date/time type.
     *
     * @param type type to check
     *
     * @return true if type accepts dates
     */
    private boolean isTimeType(int type) {
        int tempType;
        
        tempType = ExtendedTypes.getInstance().
            getStandardAlias(properties.getProperty(
            "dbmonster.jdbc.driver"), type);
        
        return (tempType == Types.DATE)
            || (tempType == Types.TIME)
            || (tempType == Types.TIMESTAMP);
    }

    /**
     * Checks if the type is boolean
     *
     * @param type type to check
     *
     * @return true if type is boolean
     */
    private boolean isBooleanType(int type) {
        int tempType;
        
        tempType = ExtendedTypes.getInstance().
            getStandardAlias(properties.getProperty(
            "dbmonster.jdbc.driver"), type);
        
        return (tempType == Types.BIT)
            || (tempType == Types.BOOLEAN);
    }
    
    /**
     * Checks if the type is binary, blob, longvarbinary or varbinary
     * 
     * @param type type to check
     * 
     * @return true if type is boolean; 
     *         false otherwise
     */
    private boolean isBinaryType(int type) {
        if (type == Types.BINARY
                || type == Types.BLOB
                || type == Types.LONGVARBINARY
                || type == Types.VARBINARY) 
        {
            return true;
        } else {
            return false;
        }
    }    
}

/**
 * Simple maps a key to a value.
 */
final class PairBean {

    /**
     * Key.
     */
    private String key = null;

    /**
     * Value.
     */
    private String value = null;

    /**
     * Constructs new PairBean
     *
     * @param k key
     * @param v value
     */
    public PairBean(String k, String v) {
        key = k;
        value = v;
    }

    /**
     * Returns the key.
     *
     * @return key
     */
    public String getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param k key
     */
    public void setKey(String k) {
        key = k;
    }

    /**
     * Returns the value.
     *
     * @return value
     */
    public String getValue() {
        return value;
    }

    /**
     * Sets the value
     *
     * @param v value to set
     */
    public void setValue(String v) {
        value = v;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy