/* * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes * Copyright (C) 2014 Ludovic Pouzenc * * This file is part of SSSync. * * SSSync is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SSSync is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SSSync. If not, see */ package data.io.sql; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import data.MVDataEntry; import data.io.AbstractMVDataReader; /** * Stream-oriented reader from a particular RDBMS source. * * @author lpouzenc */ public class SQLRelDataReader extends AbstractMVDataReader { private final Connection conn; private final String request; private transient String columnNames[]; private transient ResultSet rs; private transient boolean didNext; private transient boolean hasNext; /** * Build a new reader from an existing connection and a File containing a SELECT statement. * @param dataSourceName A short string representing this reader (for logging) * @param conn A pre-established SQL data connection * @param queryFile An SQL file containing an SQL SELECT statement * @throws IOException */ public SQLRelDataReader(String dataSourceName, Connection conn, File queryFile) throws IOException { this.dataSourceName = dataSourceName; this.conn = conn; this.request = readEntireFile(queryFile); } /** * Build a new reader from an existing connection and a String containing a SELECT statement. * @param dataSourceName A short string representing this reader (for logging) * @param conn A pre-established SQL data connection * @param query A String containing an SQL SELECT statement * @throws IOException */ public SQLRelDataReader(String dataSourceName, Connection conn, String query) { this.dataSourceName = dataSourceName; this.conn = conn; this.request = query; } /** * {@inheritDoc} * Note : multiple iterators on the same instance are not supported */ @Override public Iterator iterator() { try { // Reset iterator-related attributes hasNext = false; didNext = false; // Close and free any previous request result if ( rs != null ) { rs.close(); } // (Re-)Execute the SQL request Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); rs = stmt.executeQuery(request); // Get the column names ResultSetMetaData rsmd = rs.getMetaData(); columnNames = new String[rsmd.getColumnCount()]; for (int i = 0; i < columnNames.length ; i++) { // Java SQL : all indices starts at 1 (it sucks !) columnNames[i] = rsmd.getColumnName(i+1); } } catch (SQLException e) { throw new RuntimeException("Could not execute query : " + e.getMessage() + "\n" + request ); } return this; } /** * {@inheritDoc} */ @Override public boolean hasNext() { // java.sql.ResultSet don't implement Iterable interface at all // It's next() don't return anything except hasNext() result but it moves the cursor ! if (!didNext) { try { hasNext = rs.next(); } catch (SQLException e) { throw new RuntimeException(e); } didNext = true; } return hasNext; } /** * {@inheritDoc} */ @Override public MVDataEntry next() { MVDataEntry result = null; try { if (!didNext) { rs.next(); } didNext = false; //TODO Instead of always use the first col, user could choose a specific columnName like in LDAP String key = rs.getString(1); result = new MVDataEntry(key); for (int i = 0; i < columnNames.length ; i++) { // Java SQL : all indices starts at 1 (it sucks !) result.splitAndPut(columnNames[i], rs.getString(i+1), ";"); // TODO regex should be an option } } catch (SQLException e) { throw new RuntimeException("Exception while reading next line in SQL resultset", e); } return result; } /** * Helper function to load and entire file as a String. * @param file * @return * @throws IOException */ private static String readEntireFile(File file) throws IOException { FileReader input = new FileReader(file); StringBuilder contents = new StringBuilder(); char[] buffer = new char[4096]; int read = 0; do { contents.append(buffer, 0, read); read = input.read(buffer); } while (read >= 0); input.close(); return contents.toString(); } }