Sleuth Kit Java Bindings (JNI) 4.14.0
Java bindings for using The Sleuth Kit
Loading...
Searching...
No Matches
ScoringManager.java
Go to the documentation of this file.
1/*
2 * Sleuth Kit Data Model
3 *
4 * Copyright 2020 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.datamodel;
20
21import java.sql.PreparedStatement;
22import java.sql.ResultSet;
23import java.sql.SQLException;
24import java.sql.Statement;
25import java.util.ArrayList;
26import java.util.Collections;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Optional;
30import java.util.Map;
31import java.util.Set;
32import java.util.logging.Logger;
33import java.util.stream.Collectors;
34import org.sleuthkit.datamodel.Score.Priority;
35import org.sleuthkit.datamodel.Score.Significance;
36import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
37import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
38import org.sleuthkit.datamodel.TskData.DbType;
39
45public class ScoringManager {
46
47 private static final Logger LOGGER = Logger.getLogger(ScoringManager.class.getName());
48
49 private final SleuthkitCase db;
50
57 ScoringManager(SleuthkitCase skCase) {
58 this.db = skCase;
59 }
60
70 public Score getAggregateScore(long objId) throws TskCoreException {
71 db.acquireSingleUserCaseReadLock();
72 try (CaseDbConnection connection = db.getConnection()) {
73 return getAggregateScore(objId, false, connection).orElse(Score.SCORE_UNKNOWN);
74 } finally {
75 db.releaseSingleUserCaseReadLock();
76 }
77 }
78
89 public Map<Long, Score> getAggregateScores(List<Long> objIds) throws TskCoreException {
90
91 if (objIds.isEmpty()) {
92 return Collections.emptyMap();
93 }
94
95 // We need to deduplicate the list of object IDs. Otherwise the map
96 // below breaks and throws an exception.
97 Set<Long> set = new HashSet<>(objIds);
98
99 String queryString = "SELECT obj_id, significance, priority FROM tsk_aggregate_score WHERE obj_id in "
100 + set.stream().map(l -> l.toString()).collect(Collectors.joining(",", "(", ")"));
101
102 Map<Long, Score> results = set.stream().collect(Collectors.toMap( key -> key, key -> Score.SCORE_UNKNOWN));
103 db.acquireSingleUserCaseReadLock();
104 try (CaseDbConnection connection = db.getConnection()) {
105 try (Statement s = connection.createStatement(); ResultSet rs = connection.executeQuery(s, queryString)) {
106 while (rs.next()) {
107 Long objId = rs.getLong("obj_id");
108 Score score = new Score(Significance.fromID(rs.getInt("significance")), Priority.fromID(rs.getInt("priority")));
109 results.put(objId, score);
110 }
111 } catch (SQLException ex) {
112 throw new TskCoreException("SQLException thrown while running query: " + queryString, ex);
113 }
114 } finally {
115 db.releaseSingleUserCaseReadLock();
116 }
117 return results;
118 }
119
120
133 private Optional<Score> getAggregateScore(long objId, boolean forUpdate, CaseDbTransaction transaction) throws TskCoreException {
134 CaseDbConnection connection = transaction.getConnection();
135 return getAggregateScore(objId, forUpdate, connection);
136 }
137
149 private Optional<Score> getAggregateScore(long objId, boolean forUpdate, CaseDbConnection connection) throws TskCoreException {
150 String queryString = "SELECT significance, priority FROM tsk_aggregate_score WHERE obj_id = " + objId + (forUpdate? " FOR UPDATE " : "");
151 try (Statement s = connection.createStatement(); ResultSet rs = connection.executeQuery(s, queryString)) {
152 if (rs.next()) {
153 return Optional.of(new Score(Significance.fromID(rs.getInt("significance")), Priority.fromID(rs.getInt("priority"))));
154 } else {
155 return Optional.empty();
156 }
157 } catch (SQLException ex) {
158 throw new TskCoreException("SQLException thrown while running query: " + queryString, ex);
159 }
160 }
161
162
175 private void setAggregateScore(long objId, Long dataSourceObjectId, Score score, boolean updateOnly, CaseDbTransaction transaction) throws TskCoreException {
176
177 if (updateOnly) {
178 String updateSQLString = " UPDATE tsk_aggregate_score SET significance = ?, priority = ? where obj_id = ?" ;
179
180 CaseDbConnection connection = transaction.getConnection();
181 try {
182 PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQLString, Statement.NO_GENERATED_KEYS);
183 preparedStatement.clearParameters();
184
185 preparedStatement.setInt(1, score.getSignificance().getId());
186 preparedStatement.setInt(2, score.getPriority().getId());
187
188 preparedStatement.setLong(3, objId);
189
190 connection.executeUpdate(preparedStatement);
191 } catch (SQLException ex) {
192 throw new TskCoreException(String.format("Error updating aggregate score, query: %s for objId = %d", updateSQLString, objId), ex);//NON-NLS
193 }
194 } else {
195
196 String insertSQLString = "INSERT INTO tsk_aggregate_score (obj_id, data_source_obj_id, significance , priority) VALUES (?, ?, ?, ?)"
197 + " ON CONFLICT (obj_id) DO UPDATE SET significance = ?, priority = ?";
198
199 CaseDbConnection connection = transaction.getConnection();
200 try {
201 PreparedStatement preparedStatement = connection.getPreparedStatement(insertSQLString, Statement.NO_GENERATED_KEYS);
202 preparedStatement.clearParameters();
203
204 preparedStatement.setLong(1, objId);
205 if (dataSourceObjectId != null) {
206 preparedStatement.setLong(2, dataSourceObjectId);
207 } else {
208 preparedStatement.setNull(2, java.sql.Types.NULL);
209 }
210 preparedStatement.setInt(3, score.getSignificance().getId());
211 preparedStatement.setInt(4, score.getPriority().getId());
212
213 preparedStatement.setInt(5, score.getSignificance().getId());
214 preparedStatement.setInt(6, score.getPriority().getId());
215
216 connection.executeUpdate(preparedStatement);
217 } catch (SQLException ex) {
218 throw new TskCoreException(String.format("Error updating aggregate score, query: %s for objId = %d", insertSQLString, objId), ex);//NON-NLS
219 }
220 }
221 }
222
223
224
239 Score updateAggregateScoreAfterAddition(long objId, Long dataSourceObjectId, Score newResultScore, CaseDbTransaction transaction) throws TskCoreException {
240
241 /* get an exclusive write lock on the DB before we read anything so that we know we are
242 * the only one reading existing scores and updating. The risk is that two computers
243 * could update the score and the aggregate score ends up being incorrect.
244 *
245 * NOTE: The alternative design is to add a 'version' column for opportunistic locking
246 * and calculate these outside of a transaction. We opted for table locking for performance
247 * reasons so that we can still add the analysis results in a batch. That remains an option
248 * if we get into deadlocks with the current design.
249 */
250
251 // Get the current score
252 // Will get a "FOR UPDATE" lock in postgresql
253 Optional<Score> oCurrentAggregateScore = ScoringManager.this.getAggregateScore(objId, db.getDatabaseType().equals(DbType.POSTGRESQL), transaction);
254
255 Score currentAggregateScore = oCurrentAggregateScore.orElse(Score.SCORE_UNKNOWN);
256
257 // If current score is Unknown And newscore is not Unknown - allow None (good) to be recorded
258 // or if the new score is higher than the current score
259 if ( (currentAggregateScore.compareTo(Score.SCORE_UNKNOWN) == 0 && newResultScore.compareTo(Score.SCORE_UNKNOWN) != 0)
260 || (Score.getScoreComparator().compare(newResultScore, currentAggregateScore) > 0)) {
261 setAggregateScore(objId, dataSourceObjectId, newResultScore, oCurrentAggregateScore.isPresent(), transaction); // If score is present, do an update.
262 // register score change in the transaction.
263 transaction.registerScoreChange(new ScoreChange(objId, dataSourceObjectId, currentAggregateScore, newResultScore));
264 return newResultScore;
265 } else {
266 // return the current score
267 return currentAggregateScore;
268 }
269 }
270
281 Score updateAggregateScoreAfterDeletion(long objId, Long dataSourceObjectId, CaseDbTransaction transaction) throws TskCoreException {
282
283 CaseDbConnection connection = transaction.getConnection();
284
285 /* get an exclusive write lock on the DB before we read anything so that we know we are
286 * the only one reading existing scores and updating. The risk is that two computers
287 * could update the score and the aggregate score ends up being incorrect.
288 *
289 * NOTE: The alternative design is to add a 'version' column for opportunistic locking
290 * and calculate these outside of a transaction. We opted for table locking for performance
291 * reasons so that we can still add the analysis results in a batch. That remains an option
292 * if we get into deadlocks with the current design.
293 */
294
295 // Get the current score
296 Optional<Score> oCurrentAggregateScore = ScoringManager.this.getAggregateScore(objId, db.getDatabaseType().equals(DbType.POSTGRESQL), transaction);
297
298 Score currentScore = oCurrentAggregateScore.orElse(Score.SCORE_UNKNOWN);
299
300 // Calculate the score from scratch by getting all of them and getting the highest
301 List<AnalysisResult> analysisResults = db.getBlackboard().getAnalysisResults(objId, connection);
302 Score newScore = Score.SCORE_UNKNOWN;
303 for (AnalysisResult iter : analysisResults) {
304 if (iter.ignoreResult()) {
305 continue;
306 }
307
308 Score iterScore = iter.getScore();
309 if (Score.getScoreComparator().compare(iterScore, newScore) > 0) {
310 newScore = iterScore;
311 }
312 }
313
314 // get the maximum score of the calculated aggregate score of analysis results
315 // or the score derived from the maximum known status of a content tag on this content.
316 Optional<Score> tagScore = db.getTaggingManager().getMaxTagType(objId, transaction)
317 .map(knownStatus -> TaggingManager.getTagScore(knownStatus));
318
319 if (tagScore.isPresent() && Score.getScoreComparator().compare(tagScore.get(), newScore) > 0) {
320 newScore = tagScore.get();
321 }
322
323 // only change the DB if we got a new score.
324 if (newScore.compareTo(currentScore) != 0) {
325 setAggregateScore(objId, dataSourceObjectId, newScore, oCurrentAggregateScore.isPresent(), transaction);
326
327 // register the score change with the transaction so an event can be fired for it.
328 transaction.registerScoreChange(new ScoreChange(objId, dataSourceObjectId, currentScore, newScore));
329 }
330 return newScore;
331 }
332
343 public long getContentCount(long dataSourceObjectId, Score.Significance significance) throws TskCoreException {
344 db.acquireSingleUserCaseReadLock();
345 try (CaseDbConnection connection = db.getConnection()) {
346 return getContentCount(dataSourceObjectId, significance, connection);
347 } finally {
348 db.releaseSingleUserCaseReadLock();
349 }
350 }
351
352
365 private long getContentCount(long dataSourceObjectId, Score.Significance significance, CaseDbConnection connection) throws TskCoreException {
366 String queryString = "SELECT COUNT(obj_id) AS count FROM tsk_aggregate_score"
367 + " WHERE data_source_obj_id = " + dataSourceObjectId
368 + " AND significance = " + significance.getId();
369
370 try (Statement statement = connection.createStatement();
371 ResultSet resultSet = connection.executeQuery(statement, queryString);) {
372
373 long count = 0;
374 if (resultSet.next()) {
375 count = resultSet.getLong("count");
376 }
377 return count;
378 } catch (SQLException ex) {
379 throw new TskCoreException("Error getting count of items with significance = " + significance.toString(), ex);
380 }
381 }
382
393 public List<Content> getContent(long dataSourceObjectId, Score.Significance significance) throws TskCoreException {
394 db.acquireSingleUserCaseReadLock();
395 try (CaseDbConnection connection = db.getConnection()) {
396 return getContent(dataSourceObjectId, significance, connection);
397 } finally {
398 db.releaseSingleUserCaseReadLock();
399 }
400 }
401
414 private List<Content> getContent(long dataSourceObjectId, Score.Significance significance, CaseDbConnection connection) throws TskCoreException {
415 String queryString = "SELECT obj_id FROM tsk_aggregate_score"
416 + " WHERE data_source_obj_id = " + dataSourceObjectId
417 + " AND significance = " + significance.getId();
418
419 try (Statement statement = connection.createStatement();
420 ResultSet resultSet = connection.executeQuery(statement, queryString);) {
421
422 List<Content> items = new ArrayList<>();
423 while (resultSet.next()) {
424 long objId = resultSet.getLong("obj_id");
425 items.add(db.getContentById(objId));
426 }
427 return items;
428 } catch (SQLException ex) {
429 throw new TskCoreException("Error getting list of items with significance = " + significance.toString(), ex);
430 }
431 }
432}
static final Score SCORE_UNKNOWN
Definition Score.java:213
long getContentCount(long dataSourceObjectId, Score.Significance significance)
Map< Long, Score > getAggregateScores(List< Long > objIds)
List< Content > getContent(long dataSourceObjectId, Score.Significance significance)
static Priority fromID(int id)
Definition Score.java:184
static Significance fromID(int id)
Definition Score.java:118

Copyright © 2011-2024 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.