19 package org.sleuthkit.autopsy.timeline.events.db;
21 import com.google.common.base.Stopwatch;
22 import com.google.common.collect.HashMultimap;
23 import com.google.common.collect.SetMultimap;
25 import java.sql.Connection;
26 import java.sql.DriverManager;
27 import java.sql.PreparedStatement;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.sql.Statement;
31 import java.sql.Types;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
41 import java.util.Objects;
43 import java.util.TimeZone;
44 import java.util.concurrent.locks.Lock;
45 import java.util.concurrent.locks.ReentrantReadWriteLock;
46 import java.util.logging.Level;
47 import java.util.stream.Collectors;
48 import org.apache.commons.lang3.StringUtils;
49 import org.joda.time.DateTimeZone;
50 import org.joda.time.Interval;
51 import org.joda.time.Period;
52 import org.openide.util.Exceptions;
72 import org.sqlite.SQLiteJDBCLoader;
114 Class.forName(
"org.sqlite.JDBC");
115 }
catch (ClassNotFoundException ex) {
116 LOGGER.log(Level.SEVERE,
"Failed to load sqlite JDBC driver", ex);
131 EventDB eventDB =
new EventDB(dbPath + File.separator +
"events.db");
134 }
catch (SQLException ex) {
135 LOGGER.log(Level.SEVERE,
"sql error creating database connection", ex);
137 }
catch (Exception ex) {
138 LOGGER.log(Level.SEVERE,
"error creating database connection", ex);
143 static List<Integer> getActiveSubTypes(
TypeFilter filter) {
151 return Collections.emptyList();
155 static String getSQLWhere(IntersectionFilter filter) {
156 return filter.getSubFilters().stream()
158 .map(EventDB::getSQLWhere)
159 .collect(Collectors.joining(
" and ",
"( ",
")"));
162 static String getSQLWhere(UnionFilter filter) {
163 return filter.getSubFilters().stream()
165 .map(EventDB::getSQLWhere)
166 .collect(Collectors.joining(
" or ",
"( ",
")"));
173 if (filter == null) {
176 result = getSQLWhere((HideKnownFilter) filter);
178 result = getSQLWhere((TextFilter) filter);
180 result = getSQLWhere((TypeFilter) filter);
182 result = getSQLWhere((IntersectionFilter) filter);
184 result = getSQLWhere((UnionFilter) filter);
188 result = StringUtils.deleteWhitespace(result).equals(
"(1and1and1)") ?
"1" : result;
201 if (StringUtils.isBlank(filter.
getText())) {
204 String strip = StringUtils.strip(filter.
getText());
205 return "((" + MED_DESCRIPTION_COLUMN +
" like '%" + strip +
"%') or ("
206 + FULL_DESCRIPTION_COLUMN +
" like '%" + strip +
"%') or ("
207 + SHORT_DESCRIPTION_COLUMN +
" like '%" + strip +
"%'))";
231 return "(" + SUB_TYPE_COLUMN +
" in (" + StringUtils.join(getActiveSubTypes(filter),
",") +
"))";
234 private volatile Connection
con;
252 private final ReentrantReadWriteLock
rwLock =
new ReentrantReadWriteLock(
true);
254 private final Lock
DBLock = rwLock.writeLock();
256 private EventDB(String dbPath)
throws SQLException, Exception {
272 Interval span = null;
274 try (Statement stmt = con.createStatement();
277 ResultSet rs = stmt.executeQuery(
"select Min(time), Max(time) from events where event_id in (" + StringUtils.join(eventIDs,
", ") +
")");) {
279 span =
new Interval(rs.getLong(
"Min(time)"), rs.getLong(
"Max(time)") + 1, DateTimeZone.UTC);
282 }
catch (SQLException ex) {
283 LOGGER.log(Level.SEVERE,
"Error executing get spanning interval query.", ex);
290 EventTransaction beginTransaction() {
291 return new EventTransaction();
299 }
catch (SQLException ex) {
300 LOGGER.log(Level.WARNING,
"Failed to close connection to evetns.db", ex);
306 void commitTransaction(EventTransaction tr, Boolean notify) {
308 throw new IllegalArgumentException(
"can't close already closed transaction");
313 int countAllEvents() {
317 try (ResultSet rs = con.createStatement().executeQuery(
"select count(*) as count from events")) {
319 result = rs.getInt(
"count");
322 }
catch (SQLException ex) {
323 Exceptions.printStackTrace(ex);
330 Map<EventType, Long> countEvents(ZoomParams params) {
331 if (params.getTimeRange() != null) {
332 return countEvents(params.getTimeRange().getStartMillis() / 1000, params.getTimeRange().getEndMillis() / 1000, params.getFilter(), params.getTypeZoomLevel());
334 return Collections.emptyMap();
353 void dbReadUnlock() {
368 void dbWriteUnlock() {
376 try (Statement createStatement = con.createStatement()) {
377 createStatement.execute(
"drop table if exists events");
378 }
catch (SQLException ex) {
379 LOGGER.log(Level.SEVERE,
"could not drop old events table", ex);
385 List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
386 return getAggregatedEvents(params.getTimeRange(), params.getFilter(), params.getTypeZoomLevel(), params.getDescrLOD());
389 Interval getBoundingEventsInterval(Interval timeRange, Filter filter) {
390 long start = timeRange.getStartMillis() / 1000;
391 long end = timeRange.getEndMillis() / 1000;
392 final String sqlWhere = getSQLWhere(filter);
395 try (Statement stmt = con.createStatement();
396 ResultSet rs = stmt.executeQuery(
" select (select Max(time) from events where time <=" + start +
" and " + sqlWhere +
") as start,(select Min(time) from events where time >= " + end +
" and " + sqlWhere +
") as end")) {
399 long start2 = rs.getLong(
"start");
400 long end2 = rs.getLong(
"end");
406 return new Interval(start2 * 1000, (end2 + 1) * 1000, TimeLineController.getJodaTimeZone());
408 }
catch (SQLException ex) {
409 LOGGER.log(Level.SEVERE,
"Failed to get MIN time.", ex);
416 TimeLineEvent getEventById(Long eventID) {
417 TimeLineEvent result = null;
420 getEventByIDStmt.clearParameters();
421 getEventByIDStmt.setLong(1, eventID);
422 try (ResultSet rs = getEventByIDStmt.executeQuery()) {
428 }
catch (SQLException sqlEx) {
429 LOGGER.log(Level.SEVERE,
"exception while querying for event with id = " + eventID, sqlEx);
436 Set<Long> getEventIDs(Interval timeRange, Filter filter) {
437 return getEventIDs(timeRange.getStartMillis() / 1000, timeRange.getEndMillis() / 1000, filter);
440 Set<Long> getEventIDs(Long startTime, Long endTime, Filter filter) {
441 if (Objects.equals(startTime, endTime)) {
444 Set<Long> resultIDs =
new HashSet<>();
447 final String query =
"select event_id from events where time >= " + startTime +
" and time <" + endTime +
" and " + getSQLWhere(filter);
449 try (Statement stmt = con.createStatement();
450 ResultSet rs = stmt.executeQuery(query)) {
453 resultIDs.add(rs.getLong(EVENT_ID_COLUMN));
456 }
catch (SQLException sqlEx) {
457 LOGGER.log(Level.SEVERE,
"failed to execute query for event ids in range", sqlEx);
465 long getLastArtfactID() {
466 return getDBInfo(LAST_ARTIFACT_ID_KEY, -1);
469 long getLastObjID() {
470 return getDBInfo(LAST_OBJECT_ID_KEY, -1);
476 try (ResultSet rs = getMaxTimeStmt.executeQuery()) {
478 return rs.getLong(
"max");
480 }
catch (SQLException ex) {
481 LOGGER.log(Level.SEVERE,
"Failed to get MAX time.", ex);
491 try (ResultSet rs = getMinTimeStmt.executeQuery()) {
493 return rs.getLong(
"min");
495 }
catch (SQLException ex) {
496 LOGGER.log(Level.SEVERE,
"Failed to get MIN time.", ex);
503 boolean getWasIngestRunning() {
504 return getDBInfo(WAS_INGEST_RUNNING_KEY, 0) != 0;
514 final synchronized void initializeDB() {
521 }
catch (SQLException ex) {
522 LOGGER.log(Level.SEVERE,
"problem accessing database", ex);
527 try (Statement stmt = con.createStatement()) {
528 String sql =
"CREATE TABLE if not exists db_info "
531 +
"PRIMARY KEY (key))";
533 }
catch (SQLException ex) {
534 LOGGER.log(Level.SEVERE,
"problem creating db_info table", ex);
537 try (Statement stmt = con.createStatement()) {
538 String sql =
"CREATE TABLE if not exists events "
539 +
" (event_id INTEGER PRIMARY KEY, "
540 +
" file_id INTEGER, "
541 +
" artifact_id INTEGER, "
543 +
" sub_type INTEGER, "
544 +
" base_type INTEGER, "
545 +
" full_description TEXT, "
546 +
" med_description TEXT, "
547 +
" short_description TEXT, "
548 +
" known_state INTEGER)";
550 }
catch (SQLException ex) {
551 LOGGER.log(Level.SEVERE,
"problem creating database table", ex);
554 try (Statement stmt = con.createStatement()) {
555 String sql =
"CREATE INDEX if not exists file_idx ON events(file_id)";
557 }
catch (SQLException ex) {
558 LOGGER.log(Level.SEVERE,
"problem creating file_idx", ex);
560 try (Statement stmt = con.createStatement()) {
561 String sql =
"CREATE INDEX if not exists artifact_idx ON events(artifact_id)";
563 }
catch (SQLException ex) {
564 LOGGER.log(Level.SEVERE,
"problem creating artifact_idx", ex);
574 try (Statement stmt = con.createStatement()) {
575 String sql =
"CREATE INDEX if not exists sub_type_idx ON events(sub_type, time)";
577 }
catch (SQLException ex) {
578 LOGGER.log(Level.SEVERE,
"problem creating sub_type_idx", ex);
581 try (Statement stmt = con.createStatement()) {
582 String sql =
"CREATE INDEX if not exists base_type_idx ON events(base_type, time)";
584 }
catch (SQLException ex) {
585 LOGGER.log(Level.SEVERE,
"problem creating base_type_idx", ex);
588 try (Statement stmt = con.createStatement()) {
589 String sql =
"CREATE INDEX if not exists known_idx ON events(known_state)";
591 }
catch (SQLException ex) {
592 LOGGER.log(Level.SEVERE,
"problem creating known_idx", ex);
597 "INSERT INTO events (file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state) "
598 +
"VALUES (?,?,?,?,?,?,?,?,?)");
602 getEventByIDStmt =
prepareStatement(
"select * from events where event_id = ?");
603 recordDBInfoStmt =
prepareStatement(
"insert or replace into db_info (key, value) values (?, ?)");
604 getDBInfoStmt =
prepareStatement(
"select value from db_info where key = ?");
605 }
catch (SQLException sQLException) {
606 LOGGER.log(Level.SEVERE,
"failed to prepareStatment", sQLException);
615 void insertEvent(
long time, EventType type, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known) {
616 EventTransaction trans = beginTransaction();
617 insertEvent(time, type, objID, artifactID, fullDescription, medDescription, shortDescription, known, trans);
618 commitTransaction(trans,
true);
627 void insertEvent(
long time, EventType type, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, EventTransaction tr) {
629 throw new IllegalArgumentException(
"can't update database with closed transaction");
634 typeNum = RootEventType.allTypes.indexOf(type);
635 superTypeNum = type.getSuperType().ordinal();
641 insertRowStmt.clearParameters();
643 insertRowStmt.setLong(1, objID);
645 insertRowStmt.setNull(1, Types.INTEGER);
647 if (artifactID != null) {
648 insertRowStmt.setLong(2, artifactID);
650 insertRowStmt.setNull(2, Types.INTEGER);
652 insertRowStmt.setLong(3, time);
655 insertRowStmt.setInt(4, typeNum);
657 insertRowStmt.setNull(4, Types.INTEGER);
660 insertRowStmt.setInt(5, superTypeNum);
661 insertRowStmt.setString(6, fullDescription);
662 insertRowStmt.setString(7, medDescription);
663 insertRowStmt.setString(8, shortDescription);
665 insertRowStmt.setByte(9, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
667 insertRowStmt.executeUpdate();
669 }
catch (SQLException ex) {
670 LOGGER.log(Level.SEVERE,
"failed to insert event", ex);
676 boolean isClosed() throws SQLException {
680 return con.isClosed();
685 if (con == null || con.isClosed()) {
686 con = DriverManager.getConnection(
"jdbc:sqlite:" + dbPath);
688 }
catch (SQLException ex) {
689 LOGGER.log(Level.WARNING,
"Failed to open connection to events.db", ex);
693 void recordLastArtifactID(
long lastArtfID) {
697 void recordLastObjID(Long lastObjID) {
701 void recordWasIngestRunning(
boolean wasIngestRunning) {
702 recordDBInfo(WAS_INGEST_RUNNING_KEY, (wasIngestRunning ? 1 : 0));
705 void rollBackTransaction(EventTransaction trans) {
709 boolean tableExists() {
711 try (Statement createStatement = con.createStatement();
712 ResultSet executeQuery = createStatement.executeQuery(
"SELECT name FROM sqlite_master WHERE type='table' AND name='events'")) {
713 if (executeQuery.getString(
"name").equals(
"events") ==
false) {
716 }
catch (SQLException ex) {
717 Exceptions.printStackTrace(ex);
723 for (PreparedStatement pStmt : preparedStatements) {
731 try (Statement statement = con.createStatement()) {
733 statement.execute(
"PRAGMA synchronous = OFF;");
736 statement.execute(
"PRAGMA count_changes = OFF;");
738 statement.execute(
"PRAGMA temp_store = MEMORY");
740 statement.execute(
"PRAGMA cache_size = 50000");
742 statement.execute(
"PRAGMA auto_vacuum = 0");
744 statement.execute(
"PRAGMA read_uncommitted = True;");
750 LOGGER.log(Level.INFO, String.format(
"sqlite-jdbc version %s loaded in %s mode",
751 SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode()
752 ?
"native" :
"pure-java"));
753 }
catch (Exception exception) {
760 rs.getLong(FILE_ID_COLUMN),
761 rs.getLong(ARTIFACT_ID_COLUMN),
762 rs.getLong(TIME_COLUMN),
764 rs.getString(FULL_DESCRIPTION_COLUMN),
765 rs.getString(MED_DESCRIPTION_COLUMN),
766 rs.getString(SHORT_DESCRIPTION_COLUMN),
788 if (Objects.equals(startTime, endTime)) {
792 Map<EventType, Long> typeMap =
new HashMap<>();
798 final String queryString =
"select count(*), " + (useSubTypes ? SUB_TYPE_COLUMN :
BASE_TYPE_COLUMN)
799 +
" from events where time >= " + startTime +
" and time < " + endTime +
" and " + getSQLWhere(filter)
805 try (Statement stmt = con.createStatement();) {
806 Stopwatch stopwatch =
new Stopwatch();
808 rs = stmt.executeQuery(queryString);
815 :
BaseTypes.values()[rs.getInt(BASE_TYPE_COLUMN)];
817 typeMap.put(type, rs.getLong(
"count(*)"));
820 }
catch (Exception ex) {
821 LOGGER.log(Level.SEVERE,
"error getting count of events from db.", ex);
825 }
catch (SQLException ex) {
826 Exceptions.printStackTrace(ex);
865 long start = timeRange.getStartMillis() / 1000;
866 long end = timeRange.getEndMillis() / 1000;
867 if (Objects.equals(start, end)) {
875 Map<EventType, SetMultimap< String, AggregateEvent>> typeMap =
new HashMap<>();
879 String query =
"select strftime('" + strfTimeFormat +
"',time , 'unixepoch'" + (
TimeLineController.
getTimeZone().get().equals(TimeZone.getDefault()) ?
", 'localtime'" :
"") +
") as interval, group_concat(event_id) as event_ids, Min(time), Max(time), " + descriptionColumn +
", " + (useSubTypes ? SUB_TYPE_COLUMN :
BASE_TYPE_COLUMN)
880 +
" from events where time >= " + start +
" and time < " + end +
" and " + getSQLWhere(filter)
881 +
" group by interval, " + (useSubTypes ? SUB_TYPE_COLUMN :
BASE_TYPE_COLUMN) +
" , " + descriptionColumn
882 +
" order by Min(time)";
885 try (Statement stmt = con.createStatement();
888 Stopwatch stopwatch =
new Stopwatch();
891 rs = stmt.executeQuery(query);
900 Arrays.asList(rs.getString(
"event_ids").split(
",")),
901 rs.getString(descriptionColumn), lod);
904 SetMultimap<String, AggregateEvent> descrMap = typeMap.get(type);
905 if (descrMap == null) {
907 typeMap.put(type, descrMap);
912 }
catch (SQLException ex) {
913 Exceptions.printStackTrace(ex);
917 }
catch (SQLException ex) {
918 Exceptions.printStackTrace(ex);
924 ArrayList<AggregateEvent> aggEvents =
new ArrayList<>();
930 for (SetMultimap<String, AggregateEvent> descrMap : typeMap.values()) {
931 for (String descr : descrMap.keySet()) {
933 Iterator<AggregateEvent> iterator = descrMap.get(descr).stream()
935 -> Long.compare(o1.
getSpan().getStartMillis(), o2.
getSpan().getStartMillis()))
938 while (iterator.hasNext()) {
944 if (gap == null || gap.toDuration().getMillis() <= timeUnitLength.toDurationFrom(gap.getStart()).getMillis() / 4) {
949 aggEvents.add(current);
953 aggEvents.add(current);
965 getDBInfoStmt.setString(1, key);
967 try (ResultSet rs = getDBInfoStmt.executeQuery()) {
968 long result = defaultValue;
970 result = rs.getLong(
"value");
973 }
catch (SQLException ex) {
974 LOGGER.log(Level.SEVERE,
"failed to read key: " + key +
" from db_info", ex);
978 }
catch (SQLException ex) {
979 LOGGER.log(Level.SEVERE,
"failed to set key: " + key +
" on getDBInfoStmt ", ex);
1000 return "%Y-%m-%dT00:00:00";
1002 return "%Y-%m-%dT%H:00:00";
1004 return "%Y-%m-%dT%H:%M:00";
1006 return "%Y-%m-01T00:00:00";
1008 return "%Y-%m-%dT%H:%M:%S";
1010 return "%Y-01-01T00:00:00";
1012 return "%Y-%m-%dT%H:%M:%S";
1018 preparedStatements.add(prepareStatement);
1025 recordDBInfoStmt.setString(1, key);
1026 recordDBInfoStmt.setLong(2, value);
1027 recordDBInfoStmt.executeUpdate();
1028 }
catch (SQLException ex) {
1029 LOGGER.log(Level.SEVERE,
"failed to set dbinfo key: " + key +
" value: " + value, ex);
1056 con.setAutoCommit(
false);
1058 }
catch (SQLException ex) {
1059 LOGGER.log(Level.SEVERE,
"failed to set auto-commit to to false", ex);
1069 }
catch (SQLException ex1) {
1070 LOGGER.log(Level.SEVERE,
"Exception while attempting to rollback!!", ex1);
1087 }
catch (SQLException ex) {
1088 LOGGER.log(Level.SEVERE,
"Error commiting events.db.", ex);
1097 con.setAutoCommit(
true);
1098 }
catch (SQLException ex) {
1099 LOGGER.log(Level.SEVERE,
"Error setting auto-commit to true.", ex);
1118 super(CANNOT_HAVE_MORE_THAN_ONE_OPEN_TRANSACTION);
final ObservableList< Filter > getSubFilters()
static FileKnown valueOf(byte known)
PreparedStatement getDBInfoStmt
static EventDB getEventDB(String dbPath)
static AggregateEvent merge(AggregateEvent ag1, AggregateEvent ag2)
static final java.util.logging.Logger LOGGER
PreparedStatement insertRowStmt
synchronized String getText()
static ReadOnlyObjectProperty< TimeZone > getTimeZone()
static final String EVENT_ID_COLUMN
PreparedStatement prepareStatement(String queryString)
static final String BASE_TYPE_COLUMN
final ReentrantReadWriteLock rwLock
Interval getSpanningInterval(Collection< Long > eventIDs)
void recordDBInfo(String key, long value)
static final String MED_DESCRIPTION_COLUMN
static final String SHORT_DESCRIPTION_COLUMN
MultipleTransactionException()
static final String WAS_INGEST_RUNNING_KEY
PreparedStatement recordDBInfoStmt
List< AggregateEvent > getAggregatedEvents(Interval timeRange, Filter filter, EventTypeZoomLevel zoomLevel, DescriptionLOD lod)
String getStrfTimeFormat(TimeUnits info)
static String getSQLWhere(TextFilter filter)
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
TimeLineEvent constructTimeLineEvent(ResultSet rs)
static final String ARTIFACT_ID_COLUMN
PreparedStatement getEventByIDStmt
static final String LAST_OBJECT_ID_KEY
static final String FULL_DESCRIPTION_COLUMN
static final String TIME_COLUMN
static String getSQLWhere(Filter filter)
final Set< PreparedStatement > preparedStatements
long getDBInfo(String key, long defaultValue)
static final String LAST_ARTIFACT_ID_KEY
PreparedStatement getMinTimeStmt
static DateTimeZone getJodaTimeZone()
static String getSQLWhere(TypeFilter filter)
String getDescriptionColumn(DescriptionLOD lod)
PreparedStatement getMaxTimeStmt
static String getSQLWhere(HideKnownFilter filter)
TimeUnits getPeriodSize()
static final String FILE_ID_COLUMN
Map< EventType, Long > countEvents(Long startTime, Long endTime, Filter filter, EventTypeZoomLevel zoomLevel)
void commit(Boolean notify)
static final String SUB_TYPE_COLUMN
static Logger getLogger(String name)
static final String CANNOT_HAVE_MORE_THAN_ONE_OPEN_TRANSACTION
static final String KNOWN_COLUMN
static final List<?extends EventType > allTypes