diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 08986de050..81d537d7be 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -3080,9 +3080,33 @@ public String getBackupDatabasePath() {
* @throws TskCoreException
*/
public CaseDbTransaction beginTransaction() throws TskCoreException {
- return new CaseDbTransaction(this);
+ return new CaseDbTransaction(this, false);
}
+ /**
+ *
Create a new transaction on the case database. The transaction object
+ * that is returned can be passed to methods that take a CaseDbTransaction.
+ * The caller is responsible for calling either commit() or rollback() on
+ * the transaction object.
+ *
+ * Note that this beginning the transaction also acquires the single user
+ * case read lock, which will be automatically released when the
+ * transaction is closed.
+ *
+ * WARNING: This API should only be used if the transaction is
+ * guaranteed to only ever perform reads and no updates to the database.
+ * Undefined behavior can occur if this API is used with database updates.
+ *
+ * @return A CaseDbTransaction object.
+ *
+ * @throws TskCoreException
+ */
+ @Beta
+ public CaseDbTransaction beginReadOnlyTransaction() throws TskCoreException {
+ return new CaseDbTransaction(this, true);
+ }
+
+
/**
* Gets the case database name.
*
@@ -14458,6 +14482,7 @@ void executeCommand(DbCommand command) throws SQLException {
public static final class CaseDbTransaction {
private final CaseDbConnection connection;
+ private final boolean readOnlyTransaction;
private SleuthkitCase sleuthkitCase;
/* This class can store information about what was
@@ -14474,15 +14499,32 @@ public static final class CaseDbTransaction {
private List deletedOsAccountObjectIds = new ArrayList<>();
private List deletedResultObjectIds = new ArrayList<>();
+
// Keep track of which threads have connections to debug deadlocks
private static Set threadsWithOpenTransaction = new HashSet<>();
private static final Object threadsWithOpenTransactionLock = new Object();
- private CaseDbTransaction(SleuthkitCase sleuthkitCase) throws TskCoreException {
+ /**
+ * Constructor for a case database transaction.
+ *
+ * @param sleuthkitCase The TSK case.
+ * @param readOnlyTransaction True if the transaction will not make any
+ * writes to the database and therefore does
+ * not need the write lock.
+ *
+ * @throws TskCoreException
+ */
+ private CaseDbTransaction(SleuthkitCase sleuthkitCase, boolean readOnlyTransaction) throws TskCoreException {
this.sleuthkitCase = sleuthkitCase;
+ this.readOnlyTransaction = readOnlyTransaction;
- sleuthkitCase.acquireSingleUserCaseWriteLock();
+ if (readOnlyTransaction) {
+ sleuthkitCase.acquireSingleUserCaseReadLock();
+ } else {
+ sleuthkitCase.acquireSingleUserCaseWriteLock();
+ }
+
this.connection = sleuthkitCase.getConnection();
try {
synchronized (threadsWithOpenTransactionLock) {
@@ -14490,7 +14532,11 @@ private CaseDbTransaction(SleuthkitCase sleuthkitCase) throws TskCoreException {
threadsWithOpenTransaction.add(Thread.currentThread().getId());
}
} catch (SQLException ex) {
- sleuthkitCase.releaseSingleUserCaseWriteLock();
+ if (readOnlyTransaction) {
+ sleuthkitCase.releaseSingleUserCaseReadLock();
+ } else {
+ sleuthkitCase.releaseSingleUserCaseWriteLock();
+ }
throw new TskCoreException("Failed to create transaction on case database", ex);
}
@@ -14671,7 +14717,11 @@ public void rollback() throws TskCoreException {
*/
void close() {
this.connection.close();
- sleuthkitCase.releaseSingleUserCaseWriteLock();
+ if (readOnlyTransaction) {
+ sleuthkitCase.releaseSingleUserCaseReadLock();
+ } else {
+ sleuthkitCase.releaseSingleUserCaseWriteLock();
+ }
synchronized (threadsWithOpenTransactionLock) {
threadsWithOpenTransaction.remove(Thread.currentThread().getId());
}