org.diffkit.diff.sns.DKDBSource.java Source code

Java tutorial

Introduction

Here is the source code for org.diffkit.diff.sns.DKDBSource.java

Source

/**
 * Copyright 2010-2011 Joseph Panico
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.diffkit.diff.sns;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.diffkit.common.DKValidate;
import org.diffkit.common.annot.NotThreadSafe;
import org.diffkit.db.DKDBFlavor;
import org.diffkit.db.DKDBPrimaryKey;
import org.diffkit.db.DKDBTable;
import org.diffkit.db.DKDatabase;
import org.diffkit.diff.engine.DKColumnModel;
import org.diffkit.diff.engine.DKContext;
import org.diffkit.diff.engine.DKSource;
import org.diffkit.diff.engine.DKTableModel;
import org.diffkit.util.DKSqlUtil;
import org.diffkit.util.DKSqlUtil.ReadType;
import org.diffkit.util.DKStringUtil;
import org.diffkit.util.DKStringUtil.Quote;

/**
 * @author jpanico
 */
@NotThreadSafe
public class DKDBSource implements DKSource {
    /**
     * Gives the JDBC driver a hint as to the number of rows that should be
     * fetched from the database when more rows are needed for ResultSet objects
     * generated by this Statement. If the value specified is zero, then the hint
     * is ignored. The default value is zero.
     */
    private static final int DEFAULT_FETCH_SIZE = 1000;

    private final String _tableName;
    private final String _whereClause;
    private final DKTableModel _model;
    private final String[] _keyColumnNames;
    private final DKDatabase _database;
    private String[] _readColumnNames;
    private ReadType[] _readTypes;
    private final DKDBTable _table;
    private transient Connection _connection;
    private transient ResultSet _resultSet;
    private transient long _lastIndex;
    private transient boolean _isOpen;
    private transient boolean _isValidated;
    // DB2 does not allow repeated call to ResultSet.next() after the end of the
    // RS is reached, so we have to track that state ourselves
    private transient boolean _rsIsConsumed;
    private final Logger _log = LoggerFactory.getLogger(this.getClass());
    private final boolean _isDebug = _log.isDebugEnabled();

    public DKDBSource(String tableName_, String whereClause_, DKDatabase database_, DKTableModel model_,
            String[] keyColumnNames_, int[] readColumnIdxs_) throws SQLException {
        _log.info("tableName_->{}", tableName_);
        _log.info("whereClause_->{}", whereClause_);
        _log.info("database_->{}", database_);
        _log.info("model_->{}", model_);
        _log.info("keyColumnNames_->{}", keyColumnNames_);
        _log.info("readColumnIdxs_->{}", readColumnIdxs_);

        if (readColumnIdxs_ != null)
            throw new NotImplementedException("readColumnIdxs_ not yet implemented");
        if ((model_ != null) && (keyColumnNames_ != null))
            throw new RuntimeException(
                    String.format("does not allow both %s and %s params", "model_", "keyColumnNames_"));
        _tableName = tableName_;
        _whereClause = whereClause_;
        _database = database_;
        DKValidate.notNull(_database);
        _table = _database.getTable(tableName_);
        _log.info("table->{}", _table);
        DKValidate.notNull(_tableName);
        if (_table == null)
            throw new RuntimeException(String.format("couldn't find table named->%s", _tableName));
        this.validateKeyColumnNames(_table, keyColumnNames_);
        _keyColumnNames = keyColumnNames_;
        _model = this.getModel(model_, _keyColumnNames, _table);
        _log.info("_model->{}", _model);
        DKValidate.notNull(_model);
        this.validateModel(_model, _table);
    }

    public String getTableName() {
        return _tableName;
    }

    public String getWhereClause() {
        return _whereClause;
    }

    public String[] getKeyColumnNames() {
        return _keyColumnNames;
    }

    // @Override
    public void close(DKContext context_) throws IOException {
        this.ensureOpen();
        DKSqlUtil.close(_resultSet);
        DKSqlUtil.close(_connection);
        _resultSet = null;
        _connection = null;
        _isOpen = false;
        _rsIsConsumed = true;
    }

    // @Override
    public void open(DKContext context_) throws IOException {
        this.ensureNotOpen();
        try {
            _readColumnNames = _model.getColumnNames();
            _readTypes = _table.getReadTypes(_readColumnNames, _database);
            _connection = _database.getConnection();
            if (_database.getFlavor() != DKDBFlavor.DB2)
                _connection.setAutoCommit(false);
            _resultSet = this.createResultSet();
            if (_isDebug)
                _log.debug("_resultSet->{}", _resultSet);
            _lastIndex = -1;
            _rsIsConsumed = false;
            _isOpen = true;
        } catch (Exception e_) {
            _log.error(null, e_);
            _connection = null;
            _resultSet = null;
            _isOpen = false;
            throw new RuntimeException(e_);
        }
    }

    public String toString() {
        try {
            return String.format("%s@%x[%s,%s]", ClassUtils.getShortClassName(this.getClass()),
                    System.identityHashCode(this), this.getTableName(), this.getURI().toASCIIString());
        } catch (IOException e_) {
            throw new RuntimeException(e_);
        }
    }

    public String getDescription() {
        return String.format("%s[tableName=%s, whereClause=%s, keyColumnNames=%s, database=%s]",
                ClassUtils.getShortClassName(this.getClass()), this.getTableName(), this.getWhereClause(),
                Arrays.toString(this.getKeyColumnNames()), this.getDatabase().toString());
    }

    private DKDBTable getTable() throws SQLException {
        return _table;
    }

    public DKTableModel getModel() {
        return _model;
    }

    public DKDatabase getDatabase() {
        return _database;
    }

    public Object[] getNextRow() throws IOException {
        try {
            this.ensureOpen();
            if (_rsIsConsumed)
                return null;
            if (!_resultSet.next()) {
                _rsIsConsumed = true;
                return null;
            }
            _lastIndex++;
            return DKSqlUtil.readRow(_resultSet, _readColumnNames, _readTypes);
        } catch (Exception e_) {
            throw new RuntimeException(e_);
        }
    }

    // @Override
    public Kind getKind() {
        return Kind.DB;
    }

    // @Override
    public long getLastIndex() {
        return _lastIndex;
    }

    // @Override
    public URI getURI() throws IOException {
        try {
            return new URI(_database.getConnectionInfo().getJDBCUrl());
        } catch (URISyntaxException e_) {
            throw new RuntimeException(e_);
        }
    }

    private ResultSet createResultSet() throws SQLException {
        return DKSqlUtil.executeQuery(this.generateSelectString(), _connection, DEFAULT_FETCH_SIZE);
    }

    private String generateSelectString() throws SQLException {
        StringBuilder builder = new StringBuilder();
        DKDBTable table = this.getTable();
        builder.append(String.format("SELECT * FROM %s",
                _database.getSqlGenerator().generateQualifiedTableIdentifierString(table)));
        if (_whereClause != null) {
            String whereClause = _whereClause;
            if (_database.getCaseSensitive()) {
                String[] columnNames = _table.getColumnNames();
                whereClause = DKStringUtil.quoteAllOccurrencesOfEach(whereClause, columnNames, Quote.DOUBLE);
            }
            builder.append("\n" + whereClause);
        }
        String orderBy = this.generateOrderByClause();
        if (orderBy != null)
            builder.append("\n" + orderBy);
        return builder.toString();
    }

    private String generateOrderByClause() throws SQLException {
        String[] orderByColumnNames = this.getOrderByColumnNames();
        if (orderByColumnNames == null)
            return null;
        StringBuilder builder = new StringBuilder();
        builder.append("ORDER BY ");
        for (int i = 0; i < orderByColumnNames.length; i++) {
            // builder.append(orderByColumnNames[i]);
            builder.append(_database.getSqlGenerator().generateIdentifierString(orderByColumnNames[i]));
            if (i < orderByColumnNames.length - 1)
                builder.append(", ");
        }
        return builder.toString();
    }

    private String[] getOrderByColumnNames() throws SQLException {
        if (_keyColumnNames != null)
            return _keyColumnNames;
        DKDBTable table = this.getTable();
        DKDBPrimaryKey primaryKey = table.getPrimaryKey();
        if (primaryKey == null)
            return null;
        return primaryKey.getColumnNames();
    }

    public DKTableModel getModel(DKTableModel model_, String[] keyColumnNames_, DKDBTable dbTable_) {
        if (model_ != null)
            return model_;
        try {
            return DKTableModelUtil.createDefaultTableModel(_database.getFlavor(), dbTable_, keyColumnNames_);
        } catch (Exception e_) {
            throw new RuntimeException(e_);
        }
    }

    private void validateModel(DKTableModel model_, DKDBTable table_) {
        if (_isValidated)
            return;
        if (model_ == null)
            throw new RuntimeException("missing model_");
        if (table_ == null)
            throw new RuntimeException("missing table_");
        DKColumnModel[] columnModels = model_.getColumns();
        if ((columnModels == null) || (columnModels.length == 0))
            return;
        for (DKColumnModel columnModel : columnModels) {
            if (!table_.containsColumn(columnModel.getName()))
                throw new IllegalArgumentException(
                        String.format("modelled column->%s does not exist in table->%s", columnModel, table_));
        }
        _isValidated = true;
    }

    private void validateKeyColumnNames(DKDBTable table_, String[] keyColumnNames_) {
        if (ArrayUtils.isEmpty(keyColumnNames_))
            return;
        for (String keyColumnName : keyColumnNames_) {
            if (!table_.containsColumn(keyColumnName))
                throw new RuntimeException(
                        String.format("table->%s does not contain keyColumnName->%s", table_, keyColumnName));
        }
    }

    private void ensureOpen() {
        if (!_isOpen)
            throw new RuntimeException("not open!");
    }

    private void ensureNotOpen() {
        if (_isOpen)
            throw new RuntimeException("already open!");
    }
}