package nl.helixsoft.recordstream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Turn a {@link java.sql.ResultSet} into a {@link RecordStream}. * Underlying resources are closed automatically if you read the stream until the end. * You can also close manually with {@link #close} */ public class ResultSetRecordStream extends AbstractRecordStream { Logger log = LoggerFactory.getLogger("com.generalbioinformatics.rdf.ResultSetRecordStream"); private final ResultSet rs; private boolean closed = false; private final RecordMetaData rmd; private final Statement wrappedStatement; // may be null private final Connection wrappedConnection; // may be null @Deprecated /** * Better use the other constructor, to make sure Statement (and optionally Connection) are closed as well. */ public ResultSetRecordStream(ResultSet wrapped) throws StreamException { this(wrapped, null, null); } /** * @param wrapped ResultSet to read records from * @param st Underlying statement, will be closed when this recordstream is closed. * @param con Connection. Will be closed when this recordstream is closed. May be null if you want to keep the connection alive. */ public ResultSetRecordStream(ResultSet wrapped, Statement st, Connection con) throws StreamException { this.rs = wrapped; this.wrappedStatement = st; this.wrappedConnection = con; try { int colNum = rs.getMetaData().getColumnCount(); List<String> colnames = new ArrayList<String>(); for (int col = 1; col <= colNum; ++col) { // use getColumnLabel instead of getColumnName, so ALIASed columns work // see: http://bugs.mysql.com/bug.php?id=43684 colnames.add(rs.getMetaData().getColumnLabel(col)); } rmd = new DefaultRecordMetaData(colnames); } catch (SQLException ex) { close(); throw new StreamException(ex); } } @Override public Record getNext() throws StreamException { try { if (closed) return null; if (!rs.next()) { close(); // automatically close at end of stream. return null; } Record result = processRow(rs); return result; } catch (SQLException ex) { throw new StreamException(ex); } } /** designed to be overridden by subclasses */ protected Record processRow(ResultSet rs) throws SQLException { Object[] data = new Object[rmd.getNumCols()]; for (int col = 1; col <= rmd.getNumCols(); ++col) { data[col-1] = rs.getObject(col); } return new DefaultRecord(rmd, data); } public final void close() { if (closed) { log.debug ("Ignoring second close"); return; } closed = true; try { rs.close(); } catch (SQLException e) { log.debug ("Exception during close", e); } finally { try { if (wrappedStatement != null) { wrappedStatement.close(); } } catch (SQLException e) { log.debug ("Exception during close", e); } finally { if (wrappedConnection != null) try { log.debug ("Returning connection to the pool"); wrappedConnection.close(); } catch (SQLException e) { log.debug ("Exception during close", e); } } } } @Override public final RecordMetaData getMetaData() { return rmd; } @Override public void finalize() { // if we close at garbage collection, it's already too late... We missed a close() somewhere. if (!closed) { log.warn ("ResultSet was not closed properly"); close(); } } }