org.apache.ddlutils.platform.ModelBasedResultSetIterator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.platform.ModelBasedResultSetIterator.java

Source

package org.apache.ddlutils.platform;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.commons.beanutils.BasicDynaBean;
import org.apache.commons.beanutils.BasicDynaClass;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.ddlutils.DatabaseOperationException;
import org.apache.ddlutils.dynabean.SqlDynaBean;
import org.apache.ddlutils.dynabean.SqlDynaClass;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;

/**
 * This is an iterator that is specifically targeted at traversing result sets.
 * If the query is against a known table, then {@link org.apache.ddlutils.dynabean.SqlDynaBean} instances
 * are created from the rows, otherwise normal {@link org.apache.commons.beanutils.DynaBean} instances
 * are created.
 * 
 * @version $Revision: 289996 $
 */
public class ModelBasedResultSetIterator implements Iterator {
    /** The platform. */
    private PlatformImplBase _platform;
    /** The base result set. */
    private ResultSet _resultSet;
    /** The dyna class to use for creating beans. */
    private DynaClass _dynaClass;
    /** Whether the case of identifiers matters. */
    private boolean _caseSensitive;
    /** Maps column names to table objects as given by the query hints. */
    private Map _preparedQueryHints;
    /** Maps column names to properties. */
    private Map _columnsToProperties = new ListOrderedMap();
    /** Whether the next call to hasNext or next needs advancement. */
    private boolean _needsAdvancing = true;
    /** Whether we're already at the end of the result set. */
    private boolean _isAtEnd = false;
    /** Whether to close the statement and connection after finishing. */
    private boolean _cleanUpAfterFinish;

    /**
     * Creates a new iterator.
     * 
     * @param platform           The platform
     * @param model              The database model
     * @param resultSet          The result set
     * @param queryHints         The tables that were queried in the query that produced the given result set
     *                           (optional)
     * @param cleanUpAfterFinish Whether to close the statement and connection after finishing
     *                           the iteration, upon on exception, or when this iterator is garbage collected
     */
    public ModelBasedResultSetIterator(PlatformImplBase platform, Database model, ResultSet resultSet,
            Table[] queryHints, boolean cleanUpAfterFinish) throws DatabaseOperationException {
        if (resultSet != null) {
            _platform = platform;
            _resultSet = resultSet;
            _cleanUpAfterFinish = cleanUpAfterFinish;
            _caseSensitive = _platform.isDelimitedIdentifierModeOn();
            _preparedQueryHints = prepareQueryHints(queryHints);

            try {
                initFromMetaData(model);
            } catch (SQLException ex) {
                cleanUp();
                throw new DatabaseOperationException("Could not read the metadata of the result set", ex);
            }
        } else {
            _isAtEnd = true;
        }
    }

    /**
     * Initializes this iterator from the resultset metadata.
     * 
     * @param model The database model
     */
    private void initFromMetaData(Database model) throws SQLException {
        ResultSetMetaData metaData = _resultSet.getMetaData();
        String tableName = null;
        boolean singleKnownTable = true;

        for (int idx = 1; idx <= metaData.getColumnCount(); idx++) {
            String columnName = metaData.getColumnName(idx);
            String tableOfColumn = metaData.getTableName(idx);
            Table table = null;

            if ((tableOfColumn != null) && (tableOfColumn.length() > 0)) {
                // jConnect might return a table name enclosed in quotes
                if (tableOfColumn.startsWith("\"") && tableOfColumn.endsWith("\"")
                        && (tableOfColumn.length() > 1)) {
                    tableOfColumn = tableOfColumn.substring(1, tableOfColumn.length() - 1);
                }
                // the JDBC driver gave us enough meta data info
                table = model.findTable(tableOfColumn, _caseSensitive);
            }
            if (table == null) {
                // not enough info in the meta data of the result set, lets try the
                // user-supplied query hints
                table = (Table) _preparedQueryHints.get(_caseSensitive ? columnName : columnName.toLowerCase());
                tableOfColumn = (table == null ? null : table.getName());
            }
            if (tableName == null) {
                tableName = tableOfColumn;
            } else if (!tableName.equals(tableOfColumn)) {
                singleKnownTable = false;
            }

            String propName = columnName;

            if (table != null) {
                Column column = table.findColumn(columnName, _caseSensitive);

                if (column != null) {
                    propName = column.getName();
                }
            }
            _columnsToProperties.put(columnName, propName);
        }
        if (singleKnownTable && (tableName != null)) {
            _dynaClass = model.getDynaClassFor(tableName);
        } else {
            DynaProperty[] props = new DynaProperty[_columnsToProperties.size()];
            int idx = 0;

            for (Iterator it = _columnsToProperties.values().iterator(); it.hasNext(); idx++) {
                props[idx] = new DynaProperty((String) it.next());
            }
            _dynaClass = new BasicDynaClass("result", BasicDynaBean.class, props);
        }
    }

    /**
     * Prepares the query hints by extracting the column names and using them as keys
     * into the resulting map pointing to the corresponding table.
     *  
     * @param queryHints The query hints
     * @return The column name -> table map
     */
    private Map prepareQueryHints(Table[] queryHints) {
        Map result = new HashMap();

        for (int tableIdx = 0; (queryHints != null) && (tableIdx < queryHints.length); tableIdx++) {
            for (int columnIdx = 0; columnIdx < queryHints[tableIdx].getColumnCount(); columnIdx++) {
                String columnName = queryHints[tableIdx].getColumn(columnIdx).getName();

                if (!_caseSensitive) {
                    columnName = columnName.toLowerCase();
                }
                if (!result.containsKey(columnName)) {
                    result.put(columnName, queryHints[tableIdx]);
                }
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasNext() throws DatabaseOperationException {
        advanceIfNecessary();
        return !_isAtEnd;
    }

    /**
     * {@inheritDoc}
     */
    public Object next() throws DatabaseOperationException {
        advanceIfNecessary();
        if (_isAtEnd) {
            throw new NoSuchElementException("No more elements in the resultset");
        } else {
            try {
                DynaBean bean = _dynaClass.newInstance();
                Table table = null;

                if (bean instanceof SqlDynaBean) {
                    SqlDynaClass dynaClass = (SqlDynaClass) ((SqlDynaBean) bean).getDynaClass();

                    table = dynaClass.getTable();
                }

                for (Iterator it = _columnsToProperties.entrySet().iterator(); it.hasNext();) {
                    Map.Entry entry = (Map.Entry) it.next();
                    String columnName = (String) entry.getKey();
                    String propName = (String) entry.getValue();
                    Table curTable = table;

                    if (curTable == null) {
                        curTable = (Table) _preparedQueryHints
                                .get(_caseSensitive ? columnName : columnName.toLowerCase());
                    }

                    Object value = _platform.getObjectFromResultSet(_resultSet, columnName, curTable);

                    bean.set(propName, value);
                }
                _needsAdvancing = true;
                return bean;
            } catch (Exception ex) {
                cleanUp();
                throw new DatabaseOperationException("Exception while reading the row from the resultset", ex);
            }
        }
    }

    /**
     * Advances the iterator without materializing the object. This is the same effect as calling
     * {@link #next()} except that no object is created and nothing is read from the result set.
     */
    public void advance() {
        advanceIfNecessary();
        if (_isAtEnd) {
            throw new NoSuchElementException("No more elements in the resultset");
        } else {
            _needsAdvancing = true;
        }
    }

    /**
     * Advances the result set if necessary.
     */
    private void advanceIfNecessary() throws DatabaseOperationException {
        if (_needsAdvancing && !_isAtEnd) {
            try {
                _isAtEnd = !_resultSet.next();
                _needsAdvancing = false;
            } catch (SQLException ex) {
                cleanUp();
                throw new DatabaseOperationException("Could not retrieve next row from result set", ex);
            }
            if (_isAtEnd) {
                cleanUp();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void remove() throws DatabaseOperationException {
        try {
            _resultSet.deleteRow();
        } catch (SQLException ex) {
            cleanUp();
            throw new DatabaseOperationException("Failed to delete current row", ex);
        }
    }

    /**
     * Closes the resources (connection, statement, resultset).
     */
    public void cleanUp() {
        if (_cleanUpAfterFinish && (_resultSet != null)) {
            Connection conn = null;
            try {
                Statement stmt = _resultSet.getStatement();

                conn = stmt.getConnection();

                // also closes the resultset
                _platform.closeStatement(stmt);
            } catch (SQLException ex) {
                // we ignore it
            }
            _platform.returnConnection(conn);
            _resultSet = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    protected void finalize() throws Throwable {
        cleanUp();
    }

    /**
     * Determines whether the connection is still open.
     * 
     * @return <code>true</code> if the connection is still open
     */
    public boolean isConnectionOpen() {
        if (_resultSet == null) {
            return false;
        }
        try {
            Statement stmt = _resultSet.getStatement();
            Connection conn = stmt.getConnection();

            return !conn.isClosed();
        } catch (SQLException ex) {
            return false;
        }
    }
}