Merge pull request #4005 from dannysmyda/4092-factor-out-sqlite-reader

4092: Refactored SQLite parsing functionality out of SQLiteViewer and into SQLiteReader
This commit is contained in:
Richard Cordovano 2018-08-08 10:00:29 -04:00 committed by GitHub
commit b8364e91be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 395 additions and 198 deletions

View File

@ -22,23 +22,17 @@ import java.awt.BorderLayout;
import java.awt.Component; import java.awt.Component;
import java.awt.Cursor; import java.awt.Cursor;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.TreeMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
@ -48,14 +42,11 @@ import org.openide.util.NbBundle;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.sqlitereader.SQLiteReader;
/** /**
* A file content viewer for SQLite database files. * A file content viewer for SQLite database files.
@ -70,7 +61,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
private final SQLiteTableView selectedTableView = new SQLiteTableView(); private final SQLiteTableView selectedTableView = new SQLiteTableView();
private AbstractFile sqliteDbFile; private AbstractFile sqliteDbFile;
private File tmpDbFile; private File tmpDbFile;
private Connection connection; private SQLiteReader sqliteReader;
private int numRows; // num of rows in the selected table private int numRows; // num of rows in the selected table
private int currPage = 0; // curr page of rows being displayed private int currPage = 0; // curr page of rows being displayed
@ -347,10 +338,10 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
numEntriesField.setText(""); numEntriesField.setText("");
// close DB connection to file // close DB connection to file
if (null != connection) { if (null != sqliteReader) {
try { try {
connection.close(); sqliteReader.close();
connection = null; sqliteReader = null;
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS
} }
@ -370,41 +361,15 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
"SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.", "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
"SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.", "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
"# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",}) "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
private void processSQLiteFile() { private void processSQLiteFile() {
tablesDropdownList.removeAllItems(); tablesDropdownList.removeAllItems();
// Copy the file to temp folder
String tmpDBPathName;
try { try {
tmpDBPathName = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + sqliteDbFile.getName(); String localDiskPath = Case.getCurrentCaseThrows().getTempDirectory() +
} catch (NoCurrentCaseException ex) { File.separator + sqliteDbFile.getName();
logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS sqliteReader = new SQLiteReader(sqliteDbFile, localDiskPath);
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase());
return; Map<String, String> dbTablesMap = sqliteReader.getTableSchemas();
}
tmpDbFile = new File(tmpDBPathName);
if (! tmpDbFile.exists()) {
try {
ContentUtils.writeToFile(sqliteDbFile, tmpDbFile);
// Look for any meta files associated with this DB - WAL, SHM, etc.
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal");
findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm");
} catch (IOException | NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToExtractFile());
return;
}
}
try {
// Load the SQLite JDBC driver, if necessary.
Class.forName("org.sqlite.JDBC"); //NON-NLS
connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS
Map<String, String> dbTablesMap = getTables();
if (dbTablesMap.isEmpty()) { if (dbTablesMap.isEmpty()) {
tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry()); tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
tablesDropdownList.setEnabled(false); tablesDropdownList.setEnabled(false);
@ -413,78 +378,36 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
tablesDropdownList.addItem(tableName); tablesDropdownList.addItem(tableName);
}); });
} }
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase());
} catch (IOException | TskCoreException ex) {
logger.log(Level.SEVERE, String.format(
"Failed to create temp copy of DB file '%s' (objId=%d)", //NON-NLS
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_errorMessage_failedToExtractFile());
} catch (ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver()); "Failed to initialize JDBC SQLite '%s' (objId=%d)", //NON-NLS
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver());
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase()); "Failed to get tables from DB file '%s' (objId=%d)", //NON-NLS
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
} }
} }
/**
* Searches for a meta file associated with the give SQLite db If found,
* copies the file to the temp folder
*
* @param sqliteFile - SQLIte db file being processed
* @param metaFileName name of meta file to look for
*/
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile, String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
Case openCase = Case.getCurrentCaseThrows();
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
Services services = new Services(sleuthkitCase);
FileManager fileManager = services.getFileManager();
List<AbstractFile> metaFiles = fileManager.findFiles(sqliteFile.getDataSource(), metaFileName, sqliteFile.getParent().getName());
if (metaFiles != null) {
for (AbstractFile metaFile : metaFiles) {
String tmpMetafilePathName = openCase.getTempDirectory() + File.separator + metaFile.getName();
File tmpMetafile = new File(tmpMetafilePathName);
ContentUtils.writeToFile(metaFile, tmpMetafile);
}
}
}
/**
* Gets the table names and schemas from the SQLite database file.
*
* @return A mapping of table names to SQL CREATE TABLE statements.
*/
private Map<String, String> getTables() throws SQLException {
Map<String, String> dbTablesMap = new TreeMap<>();
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery(
"SELECT name, sql FROM sqlite_master "
+ " WHERE type= 'table' "
+ " ORDER BY name;"); //NON-NLS
while (resultSet.next()) {
String tableName = resultSet.getString("name"); //NON-NLS
String tableSQL = resultSet.getString("sql"); //NON-NLS
dbTablesMap.put(tableName, tableSQL);
}
} finally {
if (null != resultSet) {
resultSet.close();
}
if (null != statement) {
statement.close();
}
}
return dbTablesMap;
}
@NbBundle.Messages({"# {0} - tableName", @NbBundle.Messages({"# {0} - tableName",
"SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}" "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
}) })
private void selectTable(String tableName) { private void selectTable(String tableName) {
try {
try (Statement statement = connection.createStatement(); numRows = sqliteReader.getTableRowCount(tableName);
ResultSet resultSet = statement.executeQuery(
"SELECT count (*) as count FROM " + tableName)) { //NON-NLS{
numRows = resultSet.getInt("count");
numEntriesField.setText(numRows + " entries"); numEntriesField.setText(numRows + " entries");
currPage = 1; currPage = 1;
@ -504,8 +427,11 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
} }
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, String.format("Failed to load table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName)); "Failed to load table %s from DB file '%s' (objId=%d)", tableName, //NON-NLS
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_selectTable_errorText(tableName));
} }
} }
@ -513,109 +439,108 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
"SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"}) "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
private void readTable(String tableName, int startRow, int numRowsToRead) { private void readTable(String tableName, int startRow, int numRowsToRead) {
try ( try {
Statement statement = connection.createStatement(); List<Map<String, Object>> rows = sqliteReader.getRowsFromTable(
ResultSet resultSet = statement.executeQuery( tableName, startRow, numRowsToRead);
"SELECT * FROM " + tableName
+ " LIMIT " + Integer.toString(numRowsToRead)
+ " OFFSET " + Integer.toString(startRow - 1))) {
ArrayList<Map<String, Object>> rows = resultSetToArrayList(resultSet);
if (Objects.nonNull(rows)) { if (Objects.nonNull(rows)) {
selectedTableView.setupTable(rows); selectedTableView.setupTable(rows);
} else { } else {
selectedTableView.setupTable(Collections.emptyList()); selectedTableView.setupTable(Collections.emptyList());
} }
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName)); "Failed to read table %s from DB file '%s' (objId=%d)", tableName, //NON-NLS
sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_readTable_errorText(tableName));
} }
} }
@NbBundle.Messages("SQLiteViewer.BlobNotShown.message=BLOB Data not shown")
private ArrayList<Map<String, Object>> resultSetToArrayList(ResultSet rs) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
int columns = metaData.getColumnCount();
ArrayList<Map<String, Object>> rowlist = new ArrayList<>();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>(columns);
for (int i = 1; i <= columns; ++i) {
if (rs.getObject(i) == null) {
row.put(metaData.getColumnName(i), "");
} else {
if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) {
row.put(metaData.getColumnName(i), Bundle.SQLiteViewer_BlobNotShown_message());
} else {
row.put(metaData.getColumnName(i), rs.getObject(i));
}
}
}
rowlist.add(row);
}
return rowlist;
}
@NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.", /**
"SQLiteViewer.exportTableToCsv.FileName=File name: ", * Converts a sqlite table into a CSV file.
"SQLiteViewer.exportTableToCsv.TableName=Table name: " *
* @param file
* @param tableName
* @param rowMap -- A list of rows in the table, where each row is represented as a column-value
* map.
* @throws FileNotFoundException
* @throws IOException
*/
@NbBundle.Messages({
"SQLiteViewer.exportTableToCsv.FileName=File name: ",
"SQLiteViewer.exportTableToCsv.TableName=Table name: "
})
public void exportTableToCSV(File file, String tableName,
List<Map<String, Object>> rowMap) throws FileNotFoundException, IOException{
File csvFile;
String fileName = file.getName();
if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) {
csvFile = file;
} else {
csvFile = new File(file.toString() + ".csv");
}
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes());
out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes());
String header = createColumnHeader(rowMap.get(0)).concat("\n");
out.write(header.getBytes());
for (Map<String, Object> maps : rowMap) {
String row = maps.values()
.stream()
.map(Object::toString)
.collect(Collectors.joining(","))
.concat("\n");
out.write(row.getBytes());
}
}
}
@NbBundle.Messages({
"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
}) })
private void exportTableToCsv(File file) { private void exportTableToCsv(File file) {
String tableName = (String) this.tablesDropdownList.getSelectedItem(); String tableName = (String) this.tablesDropdownList.getSelectedItem();
try ( try {
Statement statement = connection.createStatement(); List<Map<String, Object>> currentTableRows =
ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName)) { sqliteReader.getRowsFromTable(tableName);
List<Map<String, Object>> currentTableRows = resultSetToArrayList(resultSet);
if (Objects.isNull(currentTableRows) || currentTableRows.isEmpty()) { if (Objects.isNull(currentTableRows) || currentTableRows.isEmpty()) {
logger.log(Level.INFO, String.format("The table %s is empty. (objId=%d)", tableName, sqliteDbFile.getId())); //NON-NLS logger.log(Level.INFO, String.format(
"The table %s is empty. (objId=%d)", tableName, //NON-NLS
sqliteDbFile.getId()));
} else { } else {
File csvFile; exportTableToCSV(file, tableName, currentTableRows);
String fileName = file.getName();
if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) {
csvFile = file;
} else {
csvFile = new File(file.toString() + ".csv");
}
try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes());
out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes());
// Set up the column names
Map<String, Object> row = currentTableRows.get(0);
StringBuffer header = new StringBuffer();
for (Map.Entry<String, Object> col : row.entrySet()) {
String colName = col.getKey();
if (header.length() > 0) {
header.append(',').append(colName);
} else {
header.append(colName);
}
}
out.write(header.append('\n').toString().getBytes());
for (Map<String, Object> maps : currentTableRows) {
StringBuffer valueLine = new StringBuffer();
maps.values().forEach((value) -> {
if (valueLine.length() > 0) {
valueLine.append(',').append(value.toString());
} else {
valueLine.append(value.toString());
}
});
out.write(valueLine.append('\n').toString().getBytes());
}
}
} }
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName)); "Failed to read table %s from DB file '%s' (objId=%d)", //NON-NLS
tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex);
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_readTable_errorText(tableName));
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS logger.log(Level.SEVERE, String.format(
MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText()); "Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS
MessageNotifyUtil.Message.error(
Bundle.SQLiteViewer_exportTableToCsv_write_errText());
} }
} }
/**
* Returns a comma seperated header string from the keys of the column
* row map.
*
* @param row -- column header row map
* @return -- comma seperated header string
*/
private String createColumnHeader(Map<String, Object> row) {
return row.entrySet()
.stream()
.map(Map.Entry::getKey)
.collect(Collectors.joining(","));
}
} }

View File

@ -0,0 +1,272 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.
*/
package org.sleuthkit.autopsy.sqlitereader;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Reads rows from SQLite tables and returns results in a list collection.
*/
public class SQLiteReader implements AutoCloseable {
private final Connection connection;
/**
* Writes data source file contents to local disk and opens a sqlite JDBC
* connection.
*
* @param sqliteDbFile Data source abstract file
* @param localDiskPath Location for database contents to be copied to
* @throws ClassNotFoundException missing SQLite JDBC class
* @throws SQLException Exception opening JDBC connection
* @throws IOException Exception writing file contents
* @throws NoCurrentCaseException Current case closed during file copying
* @throws TskCoreException Exception finding files from abstract file
*/
public SQLiteReader(AbstractFile sqliteDbFile, String localDiskPath) throws ClassNotFoundException,
SQLException, IOException, NoCurrentCaseException, TskCoreException{
writeDataSourceToLocalDisk(sqliteDbFile, localDiskPath);
connection = getDatabaseConnection(localDiskPath);
}
/**
* Copies the data source file contents to local drive for processing.
*
* @param file AbstractFile from the data source
* @param localDiskPath Local drive path to copy AbstractFile contents
* @throws IOException Exception writing file contents
* @throws NoCurrentCaseException Current case closed during file copying
* @throws TskCoreException Exception finding files from abstract file
*/
private void writeDataSourceToLocalDisk(AbstractFile file, String localDiskPath)
throws IOException, NoCurrentCaseException, TskCoreException {
File localDatabaseFile = new File(localDiskPath);
if (!localDatabaseFile.exists()) {
ContentUtils.writeToFile(file, localDatabaseFile);
// Look for any meta files associated with this DB - WAL, SHM, etc.
findAndCopySQLiteMetaFile(file, file.getName() + "-wal");
findAndCopySQLiteMetaFile(file, file.getName() + "-shm");
}
}
/**
* Searches for a meta file associated with the give SQLite database. If found,
* copies the file to the local disk folder
*
* @param sqliteFile SQLIte db file being processed
* @param metaFileName name of meta file to look for
* @throws NoCurrentCaseException Case has been closed.
* @throws TskCoreException fileManager cannot find AbstractFile files.
* @throws IOException Issue during writing to file.
*/
private void findAndCopySQLiteMetaFile(AbstractFile sqliteFile,
String metaFileName) throws NoCurrentCaseException, TskCoreException, IOException {
Case openCase = Case.getCurrentCaseThrows();
SleuthkitCase sleuthkitCase = openCase.getSleuthkitCase();
Services services = new Services(sleuthkitCase);
FileManager fileManager = services.getFileManager();
List<AbstractFile> metaFiles = fileManager.findFiles(
sqliteFile.getDataSource(), metaFileName,
sqliteFile.getParent().getName());
if (metaFiles != null) {
for (AbstractFile metaFile : metaFiles) {
String tmpMetafilePathName = openCase.getTempDirectory() +
File.separator + metaFile.getName();
File tmpMetafile = new File(tmpMetafilePathName);
ContentUtils.writeToFile(metaFile, tmpMetafile);
}
}
}
/**
* Opens a JDBC connection to the sqlite database specified by the path
* parameter.
*
* @param databasePath Local path of sqlite database
* @return Connection JDBC connection, to be maintained and closed by the reader
* @throws ClassNotFoundException missing SQLite JDBC class
* @throws SQLException Exception during opening database connection
*/
private Connection getDatabaseConnection(String databasePath)
throws ClassNotFoundException, SQLException {
// Load the SQLite JDBC driver, if necessary.
Class.forName("org.sqlite.JDBC"); //NON-NLS
return DriverManager.getConnection(
"jdbc:sqlite:" + databasePath); //NON-NLS
}
/**
* Retrieves a map view of table names to table schemas (in the form of
* CREATE TABLE statments).
*
* @return A map of table names to table schemas
* @throws SQLException
*/
public Map<String, String> getTableSchemas()
throws SQLException {
Map<String, String> dbTablesMap = new TreeMap<>();
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(
"SELECT name, sql FROM sqlite_master " //NON-NLS
+ " WHERE type= 'table' " //NON-NLS
+ " ORDER BY name;")){ //NON-NLS
while (resultSet.next()) {
String tableName = resultSet.getString("name"); //NON-NLS
String tableSQL = resultSet.getString("sql"); //NON-NLS
dbTablesMap.put(tableName, tableSQL);
}
}
return dbTablesMap;
}
/**
* Retrieves the total number of rows from a table in the SQLite database.
*
* @param tableName
* @return Row count from tableName
* @throws SQLException
*/
public Integer getTableRowCount(String tableName) throws SQLException {
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(
"SELECT count (*) as count FROM " + tableName)){ //NON-NLS
return resultSet.getInt("count"); //NON-NLS
}
}
/**
* Retrieves all rows from a given table in the SQLite database. If only a
* subset of rows are desired, see the overloaded function below.
*
* @param tableName
* @return List of rows, where each row is
* represented as a column-value map.
* @throws SQLException
*/
public List<Map<String, Object>> getRowsFromTable(String tableName) throws SQLException {
//This method does not directly call its overloaded counterpart
//since the second parameter would need to be retreived from a call to
//getTableRowCount().
try(Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName)) { //NON-NLS
return resultSetToList(resultSet);
}
}
/**
* Retrieves a subset of the rows from a given table in the SQLite database.
*
* @param tableName
* @param startRow Desired start index (rows begin at 1)
* @param numRowsToRead Number of rows past the start index
* @return List of rows, where each row is
* represented as a column-value map.
* @throws SQLException
*/
public List<Map<String, Object>> getRowsFromTable(String tableName,
int startRow, int numRowsToRead) throws SQLException{
try(Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName //NON-NLS
+ " LIMIT " + Integer.toString(numRowsToRead) //NON-NLS
+ " OFFSET " + Integer.toString(startRow - 1))) { //NON-NLS
return resultSetToList(resultSet);
}
}
/**
* Converts a ResultSet (row results from a table read) into a list.
*
* @param resultSet row results from a table read
* @return List of rows, where each row is
* represented as a column-value map.
* @throws SQLException occurs if ResultSet is closed while attempting to
* access it's data.
*/
@NbBundle.Messages("SQLiteReader.BlobNotShown.message=BLOB Data not shown")
private List<Map<String, Object>> resultSetToList(ResultSet resultSet) throws SQLException {
ResultSetMetaData metaData = resultSet.getMetaData();
int columns = metaData.getColumnCount();
List<Map<String, Object>> rowMap = new ArrayList<>();
while (resultSet.next()) {
Map<String, Object> row = new LinkedHashMap<>(columns);
for (int i = 1; i <= columns; ++i) {
if (resultSet.getObject(i) == null) {
row.put(metaData.getColumnName(i), "");
} else {
if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) {
row.put(metaData.getColumnName(i), Bundle.SQLiteReader_BlobNotShown_message());
} else {
row.put(metaData.getColumnName(i), resultSet.getObject(i));
}
}
}
rowMap.add(row);
}
return rowMap;
}
/**
* Closes underlying JDBC connection.
*
* @throws SQLException
*/
@Override
public void close() throws SQLException {
connection.close();
}
}