Fixed the temp directory file collisions during reading and refactored the localDiskPath out

This commit is contained in:
U-BASIS\dsmyda 2018-09-18 12:10:30 -04:00
parent 3bf5d1461f
commit c31044a657
4 changed files with 184 additions and 123 deletions

View File

@ -22,8 +22,10 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -32,10 +34,17 @@ import org.sleuthkit.datamodel.TskCoreException;
*/ */
public abstract class AbstractReader implements AutoCloseable { public abstract class AbstractReader implements AutoCloseable {
public AbstractReader(AbstractFile file, String localDiskPath) private final String localDiskPath;
public AbstractReader(Content file)
throws FileReaderInitException { throws FileReaderInitException {
writeDataSourceToLocalDisk(file, localDiskPath); try {
localDiskPath = getLocalDiskPath(file);
writeDataSourceToLocalDisk(file);
} catch (FileReaderInitException ex) {
throw new FileReaderInitException(ex);
}
} }
/** /**
@ -48,7 +57,7 @@ public abstract class AbstractReader implements AutoCloseable {
* @throws NoCurrentCaseException Current case closed during file copying * @throws NoCurrentCaseException Current case closed during file copying
* @throws TskCoreException Exception finding files from abstract file * @throws TskCoreException Exception finding files from abstract file
*/ */
private void writeDataSourceToLocalDisk(AbstractFile file, String localDiskPath) private void writeDataSourceToLocalDisk(Content file)
throws FileReaderInitException { throws FileReaderInitException {
try { try {
@ -61,6 +70,30 @@ public abstract class AbstractReader implements AutoCloseable {
} }
} }
public String getLocalDiskPath() {
return localDiskPath;
}
/**
* Generates a local disk path for abstract file contents to be copied. All
* file sources must be copied to local disk to be opened by abstract
* reader.
*
* @param file The database abstract file
*
* @return Valid local path for copying
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException
*
*/
private String getLocalDiskPath(Content file) throws FileReaderInitException {
try {
return Case.getCurrentCaseThrows().getTempDirectory()
+ File.separator + file.getId() + file.getName();
} catch(NoCurrentCaseException ex) {
throw new FileReaderInitException("No current case open when trying to get temp directory", ex);
}
}
/** /**
* Return the a mapping of table names to table schemas (may be in the form of * Return the a mapping of table names to table schemas (may be in the form of
* headers or create table statements for databases). * headers or create table statements for databases).

View File

@ -37,9 +37,9 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.datamodel.AbstractFile;
import com.monitorjbl.xlsx.StreamingReader; import com.monitorjbl.xlsx.StreamingReader;
import org.apache.poi.hssf.OldExcelFormatException; import org.apache.poi.hssf.OldExcelFormatException;
import org.sleuthkit.datamodel.Content;
/** /**
* Reads excel files and implements the abstract reader api for interfacing with * Reads excel files and implements the abstract reader api for interfacing with
@ -58,10 +58,10 @@ public final class ExcelReader extends AbstractReader {
private String LOCAL_DISK_PATH; private String LOCAL_DISK_PATH;
private String ACTIVE_MIME_TYPE; private String ACTIVE_MIME_TYPE;
public ExcelReader(AbstractFile file, String localDiskPath, String mimeType) public ExcelReader(Content file, String mimeType)
throws FileReaderInitException { throws FileReaderInitException {
super(file, localDiskPath); super(file);
this.LOCAL_DISK_PATH = localDiskPath; this.LOCAL_DISK_PATH = super.getLocalDiskPath();
this.ACTIVE_MIME_TYPE = mimeType; this.ACTIVE_MIME_TYPE = mimeType;
try { try {

View File

@ -19,40 +19,44 @@
package org.sleuthkit.autopsy.tabulardatareader; package org.sleuthkit.autopsy.tabulardatareader;
import org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException; import org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content;
/** /**
* Factory for creating the correct reader given the mime type of a file. * Factory for creating the correct reader given the mime type of a file.
*/ */
public final class FileReaderFactory { public final class FileReaderFactory {
private FileReaderFactory() { private FileReaderFactory() {
} }
/** /**
* Instantiates the appropriate reader given the mimeType argument. Currently * Instantiates the appropriate reader given the mimeType argument.
* supports SQLite files and Excel files (.xls and .xlsx). BIFF5 format of .xls * Currently supports SQLite files and Excel files (.xls and .xlsx). BIFF5
* is not supported. * format of .xls is not supported.
* *
* @param mimeType mimeType passed in from the ingest module * @param mimeType mimeType passed in from the ingest module g * @param file
g * @param file current file under inspection * current file under inspection
* @param localDiskPath path for abstract file contents to be written *
* @param file Content file to be copied into
*
* @return The correct reader class needed to read the file contents * @return The correct reader class needed to read the file contents
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException *
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException
*/ */
public static AbstractReader createReader(String mimeType, AbstractFile file, public static AbstractReader createReader(Content file, String mimeType) throws FileReaderInitException {
String localDiskPath) throws FileReaderInitException {
switch (mimeType) { switch (mimeType) {
case "application/x-sqlite3": case "application/x-sqlite3":
return new SQLiteReader(file, localDiskPath); return new SQLiteReader(file);
case "application/vnd.ms-excel": case "application/vnd.ms-excel":
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
try { try {
return new ExcelReader(file, localDiskPath, mimeType); return new ExcelReader(file, mimeType);
//Catches runtime exceptions being emitted from Apache //Catches runtime exceptions being emitted from Apache
//POI (such as EncryptedDocumentException) and wraps them //POI (such as EncryptedDocumentException) and wraps them
//into FileReaderInitException to be caught and logged //into FileReaderInitException to be caught and logged
//in the ingest module. //in the ingest module.
} catch(Exception poiInitException) { } catch (Exception poiInitException) {
throw new FileReaderInitException(poiInitException); throw new FileReaderInitException(poiInitException);
} }
default: default:

View File

@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -50,152 +51,166 @@ import org.sleuthkit.datamodel.TskCoreException;
* Reads sqlite databases and returns results in a list collection. * Reads sqlite databases and returns results in a list collection.
*/ */
public final class SQLiteReader extends AbstractReader { public final class SQLiteReader extends AbstractReader {
private final Connection connection; private final Connection connection;
private final static IngestServices services = IngestServices.getInstance(); private final static IngestServices services = IngestServices.getInstance();
private final static Logger logger = services.getLogger(SQLiteReader.class.getName()); private final static Logger logger = services.getLogger(SQLiteReader.class.getName());
/** /**
* Writes data source file contents to local disk and opens a sqlite JDBC * Writes data source file contents to local disk and opens a sqlite JDBC
* connection. * connection.
* *
* @param sqliteDbFile Data source abstract file * @param sqliteDbFile Data source content
* @param localDiskPath Location for database contents to be copied to *
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException * @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderInitException
*/ */
public SQLiteReader(AbstractFile sqliteDbFile, String localDiskPath) throws FileReaderInitException { public SQLiteReader(Content sqliteDbFile) throws FileReaderInitException {
super(sqliteDbFile, localDiskPath); super(sqliteDbFile);
try { try {
// Look for any meta files associated with this DB - WAL, SHM, etc. // Look for any meta files associated with this DB - WAL, SHM, etc.
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal"); findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal");
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm"); findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm");
connection = getDatabaseConnection(localDiskPath); connection = getDatabaseConnection(super.getLocalDiskPath());
} catch (ClassNotFoundException | SQLException |IOException | } catch (ClassNotFoundException | SQLException | IOException
NoCurrentCaseException | TskCoreException ex) { | NoCurrentCaseException | TskCoreException ex) {
throw new FileReaderInitException(ex); throw new FileReaderInitException(ex);
} }
} }
/** /**
* Searches for a meta file associated with the give SQLite database. If found, * Searches for a meta file associated with the give SQLite database. If
* copies the file to the local disk folder * found, copies the file to the local disk folder
* *
* @param sqliteFile file being processed * @param sqliteFile file being processed
* @param metaFileName name of meta file to look for * @param metaFileName name of meta file to look for
*
* @throws NoCurrentCaseException Case has been closed. * @throws NoCurrentCaseException Case has been closed.
* @throws TskCoreException fileManager cannot find AbstractFile files. * @throws TskCoreException fileManager cannot find AbstractFile
* @throws IOException Issue during writing to file. * files.
* @throws IOException Issue during writing to file.
*/ */
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, private void findAndCopySQLiteMetaFile(Content sqliteFile,
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException { String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
Case openCase = Case.getCurrentCaseThrows(); Case openCase = Case.getCurrentCaseThrows();
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase(); SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
Services services = new Services(sleuthkitCase); Services services = new Services(sleuthkitCase);
FileManager fileManager = services.getFileManager(); FileManager fileManager = services.getFileManager();
List<AbstractFile> metaFiles = fileManager.findFiles( List<AbstractFile> metaFiles = fileManager.findFiles(
sqliteFile.getDataSource(), metaFileName, sqliteFile.getDataSource(), metaFileName,
sqliteFile.getParent().getName()); sqliteFile.getParent().getName());
if (metaFiles != null) { if (metaFiles != null) {
for (AbstractFile metaFile : metaFiles) { for (AbstractFile metaFile : metaFiles) {
String tmpMetafilePathName = openCase.getTempDirectory() + String tmpMetafilePathName = openCase.getTempDirectory()
File.separator + metaFile.getName(); + File.separator + metaFile.getId() + metaFile.getName();
File tmpMetafile = new File(tmpMetafilePathName); File tmpMetafile = new File(tmpMetafilePathName);
ContentUtils.writeToFile(metaFile, tmpMetafile); ContentUtils.writeToFile(metaFile, tmpMetafile);
} }
} }
} }
/** /**
* Opens a JDBC connection to the sqlite database specified by the path * Opens a JDBC connection to the sqlite database specified by the path
* parameter. * parameter.
* *
* @param databasePath Local path of sqlite database * @param databasePath Local path of sqlite database
* @return Connection JDBC connection, to be maintained and closed by the reader *
* @return Connection JDBC connection, to be maintained and closed by the
* reader
*
* @throws ClassNotFoundException missing SQLite JDBC class * @throws ClassNotFoundException missing SQLite JDBC class
* @throws SQLException Exception during opening database connection * @throws SQLException Exception during opening database
* connection
*/ */
private Connection getDatabaseConnection(String databasePath) private Connection getDatabaseConnection(String databasePath)
throws ClassNotFoundException, SQLException { throws ClassNotFoundException, SQLException {
// Load the SQLite JDBC driver, if necessary. // Load the SQLite JDBC driver, if necessary.
Class.forName("org.sqlite.JDBC"); //NON-NLS Class.forName("org.sqlite.JDBC"); //NON-NLS
return DriverManager.getConnection( return DriverManager.getConnection(
"jdbc:sqlite:" + databasePath); //NON-NLS "jdbc:sqlite:" + databasePath); //NON-NLS
} }
/** /**
* Retrieves a map view of table names to table schemas (in the form of * Retrieves a map view of table names to table schemas (in the form of
* CREATE TABLE statments). * CREATE TABLE statments).
* *
* @return A map of table names to table schemas * @return A map of table names to table schemas
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException *
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException
*/ */
@Override @Override
public Map<String, String> getTableSchemas() throws FileReaderException { public Map<String, String> getTableSchemas() throws FileReaderException {
Map<String, String> dbTablesMap = new TreeMap<>(); Map<String, String> dbTablesMap = new TreeMap<>();
try (Statement statement = connection.createStatement(); try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery( ResultSet resultSet = statement.executeQuery(
"SELECT name, sql FROM sqlite_master " //NON-NLS "SELECT name, sql FROM sqlite_master " //NON-NLS
+ " WHERE type= 'table' " //NON-NLS + " WHERE type= 'table' " //NON-NLS
+ " ORDER BY name;")){ //NON-NLS + " ORDER BY name;")) { //NON-NLS
while (resultSet.next()) { while (resultSet.next()) {
String tableName = resultSet.getString("name"); //NON-NLS String tableName = resultSet.getString("name"); //NON-NLS
String tableSQL = resultSet.getString("sql"); //NON-NLS String tableSQL = resultSet.getString("sql"); //NON-NLS
dbTablesMap.put(tableName, tableSQL); dbTablesMap.put(tableName, tableSQL);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
throw new FileReaderException(ex); throw new FileReaderException(ex);
} }
return dbTablesMap; return dbTablesMap;
} }
/** /**
* Retrieves the total number of rows from a table in the SQLite database. * Retrieves the total number of rows from a table in the SQLite database.
* *
* @param tableName * @param tableName
*
* @return Row count from tableName * @return Row count from tableName
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException *
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException
*/ */
@Override @Override
public Integer getRowCountFromTable(String tableName) public Integer getRowCountFromTable(String tableName)
throws FileReaderException { throws FileReaderException {
String quotedTableName = wrapTableNameStringWithQuotes(tableName); String quotedTableName = wrapTableNameStringWithQuotes(tableName);
try (Statement statement = connection.createStatement(); try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery( ResultSet resultSet = statement.executeQuery(
"SELECT count (*) as count FROM " + quotedTableName)){ //NON-NLS "SELECT count (*) as count FROM " + quotedTableName)) { //NON-NLS
return resultSet.getInt("count"); //NON-NLS return resultSet.getInt("count"); //NON-NLS
} catch (SQLException ex) { } catch (SQLException ex) {
throw new FileReaderException(ex); throw new FileReaderException(ex);
} }
} }
/** /**
* Retrieves all rows from a given table in the SQLite database. If only a * Retrieves all rows from a given table in the SQLite database. If only a
* subset of rows are desired, see the overloaded function below. * subset of rows are desired, see the overloaded function below.
* *
* @param tableName * @param tableName
* @return List of rows, where each row is *
* represented as a column-value map. * @return List of rows, where each row is represented as a column-value
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException * map.
*
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException
*/ */
@Override @Override
public List<Map<String, Object>> getRowsFromTable(String tableName) public List<Map<String, Object>> getRowsFromTable(String tableName)
throws FileReaderException { throws FileReaderException {
//This method does not directly call its overloaded counterpart //This method does not directly call its overloaded counterpart
//since the second parameter would need to be retreived from a call to //since the second parameter would need to be retreived from a call to
//getTableRowCount(). //getTableRowCount().
String quotedTableName = wrapTableNameStringWithQuotes(tableName); String quotedTableName = wrapTableNameStringWithQuotes(tableName);
try(Statement statement = connection.createStatement(); try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery( ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + quotedTableName)) { //NON-NLS "SELECT * FROM " + quotedTableName)) { //NON-NLS
return resultSetToList(resultSet); return resultSetToList(resultSet);
@ -203,24 +218,27 @@ public final class SQLiteReader extends AbstractReader {
throw new FileReaderException(ex); throw new FileReaderException(ex);
} }
} }
/** /**
* Retrieves a subset of the rows from a given table in the SQLite database. * Retrieves a subset of the rows from a given table in the SQLite database.
* *
* @param tableName * @param tableName
* @param offset Desired start index (rows begin at 1) * @param offset Desired start index (rows begin at 1)
* @param numRowsToRead Number of rows past the start index * @param numRowsToRead Number of rows past the start index
* @return List of rows, where each row is *
* represented as a column-value map. * @return List of rows, where each row is represented as a column-value
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException * map.
*
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException
*/ */
@Override @Override
public List<Map<String, Object>> getRowsFromTable(String tableName, public List<Map<String, Object>> getRowsFromTable(String tableName,
int offset, int numRowsToRead) throws FileReaderException{ int offset, int numRowsToRead) throws FileReaderException {
String quotedTableName = wrapTableNameStringWithQuotes(tableName); String quotedTableName = wrapTableNameStringWithQuotes(tableName);
try(Statement statement = connection.createStatement(); try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery( ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + quotedTableName //NON-NLS "SELECT * FROM " + quotedTableName //NON-NLS
+ " LIMIT " + Integer.toString(numRowsToRead) //NON-NLS + " LIMIT " + Integer.toString(numRowsToRead) //NON-NLS
+ " OFFSET " + Integer.toString(offset - 1))) { //NON-NLS + " OFFSET " + Integer.toString(offset - 1))) { //NON-NLS
return resultSetToList(resultSet); return resultSetToList(resultSet);
@ -228,31 +246,34 @@ public final class SQLiteReader extends AbstractReader {
throw new FileReaderException(ex); throw new FileReaderException(ex);
} }
} }
/** /**
* Wraps table name with quotation marks in case table name contains spaces. * Wraps table name with quotation marks in case table name contains spaces.
* sqliteJDBC cannot read table names with spaces in them unless surrounded * sqliteJDBC cannot read table names with spaces in them unless surrounded
* by quotation marks. * by quotation marks.
* *
* @param tableName * @param tableName
*
* @return Input name: Result Table -> "Result Table" * @return Input name: Result Table -> "Result Table"
*/ */
private String wrapTableNameStringWithQuotes(String tableName) { private String wrapTableNameStringWithQuotes(String tableName) {
return "\"" + tableName +"\""; return "\"" + tableName + "\"";
} }
/** /**
* Converts a ResultSet (row results from a table read) into a list. * Converts a ResultSet (row results from a table read) into a list.
* *
* @param resultSet row results from a table read * @param resultSet row results from a table read
* @return List of rows, where each row is *
* represented as a column-value map. * @return List of rows, where each row is represented as a column-value
* @throws SQLException occurs if ResultSet is closed while attempting to * map.
* access it's data. *
* @throws SQLException occurs if ResultSet is closed while attempting to
* access it's data.
*/ */
@NbBundle.Messages("SQLiteReader.BlobNotShown.message=BLOB Data not shown") @NbBundle.Messages("SQLiteReader.BlobNotShown.message=BLOB Data not shown")
private List<Map<String, Object>> resultSetToList(ResultSet resultSet) throws SQLException { private List<Map<String, Object>> resultSetToList(ResultSet resultSet) throws SQLException {
ResultSetMetaData metaData = resultSet.getMetaData(); ResultSetMetaData metaData = resultSet.getMetaData();
int columns = metaData.getColumnCount(); int columns = metaData.getColumnCount();
List<Map<String, Object>> rowMap = new ArrayList<>(); List<Map<String, Object>> rowMap = new ArrayList<>();
@ -274,33 +295,36 @@ public final class SQLiteReader extends AbstractReader {
return rowMap; return rowMap;
} }
/** /**
* Returns a column view of the table. Maps the column name to a list of that * Returns a column view of the table. Maps the column name to a list of
* column's values. * that column's values.
* *
* @param tableName * @param tableName
*
* @return * @return
* @throws org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException *
* @throws
* org.sleuthkit.autopsy.tabulardatareader.AbstractReader.FileReaderException
*/ */
@Override @Override
public Map<String, List<Object>> getColumnsFromTable(String tableName) public Map<String, List<Object>> getColumnsFromTable(String tableName)
throws FileReaderException { throws FileReaderException {
String quotedTableName = wrapTableNameStringWithQuotes(tableName); String quotedTableName = wrapTableNameStringWithQuotes(tableName);
try(Statement statement = connection.createStatement(); try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery( ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + quotedTableName)) { //NON-NLS "SELECT * FROM " + quotedTableName)) { //NON-NLS
Map<String, List<Object>> columnView = new HashMap<>(); Map<String, List<Object>> columnView = new HashMap<>();
ResultSetMetaData metaData = resultSet.getMetaData(); ResultSetMetaData metaData = resultSet.getMetaData();
int columns = metaData.getColumnCount(); int columns = metaData.getColumnCount();
for(int i = 1; i <= columns; i++) { for (int i = 1; i <= columns; i++) {
columnView.put(metaData.getColumnName(i), new LinkedList<>()); columnView.put(metaData.getColumnName(i), new LinkedList<>());
} }
while (resultSet.next()) { while (resultSet.next()) {
for(int i = 1; i <= columns; i++) { for (int i = 1; i <= columns; i++) {
if (resultSet.getObject(i) == null) { if (resultSet.getObject(i) == null) {
columnView.get(metaData.getColumnName(i)).add(""); columnView.get(metaData.getColumnName(i)).add("");
} else { } else {
@ -314,7 +338,7 @@ public final class SQLiteReader extends AbstractReader {
} }
} }
} }
return columnView; return columnView;
} catch (SQLException ex) { } catch (SQLException ex) {
throw new FileReaderException(ex); throw new FileReaderException(ex);
@ -322,7 +346,7 @@ public final class SQLiteReader extends AbstractReader {
} }
/** /**
* Closes underlying JDBC connection. * Closes underlying JDBC connection.
*/ */
@Override @Override
public void close() { public void close() {