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);
}
else
{
// 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;
}
}
}