19 package org.sleuthkit.datamodel;
21 import com.google.common.annotations.Beta;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableMap;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.sql.Statement;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
35 import java.util.Objects;
36 import static java.util.Objects.isNull;
37 import java.util.Optional;
39 import java.util.stream.Collectors;
40 import org.joda.time.DateTimeZone;
41 import org.joda.time.Interval;
57 private static final ImmutableList<TimelineEventType> ROOT_CATEGORY_AND_FILESYSTEM_TYPES
74 private static final ImmutableList<TimelineEventType> PREDEFINED_EVENT_TYPES
87 private final Map<Long, TimelineEventType> eventTypeIDMap =
new HashMap<>();
102 ROOT_CATEGORY_AND_FILESYSTEM_TYPES.forEach(eventType -> eventTypeIDMap.put(eventType.getTypeID(), eventType));
106 try (
final CaseDbConnection con = caseDB.getConnection();
107 final Statement statement = con.createStatement()) {
109 con.executeUpdate(statement,
110 insertOrIgnore(
" INTO tsk_event_types(event_type_id, display_name, super_type_id) "
111 +
"VALUES( " + type.getTypeID() +
", '"
112 + escapeSingleQuotes(type.getDisplayName()) +
"',"
113 + type.getParent().getTypeID()
115 eventTypeIDMap.put(type.getTypeID(), type);
117 }
catch (SQLException ex) {
118 throw new TskCoreException(
"Failed to initialize timeline event types", ex);
136 if (eventIDs.isEmpty()) {
139 final String query =
"SELECT Min(time) as minTime, Max(time) as maxTime FROM tsk_events WHERE event_id IN (" + buildCSVString(eventIDs) +
")";
141 try (CaseDbConnection con = caseDB.getConnection();
142 Statement stmt = con.createStatement();
143 ResultSet results = stmt.executeQuery(query);) {
144 if (results.next()) {
145 return new Interval(results.getLong(
"minTime") * 1000, (results.getLong(
"maxTime") + 1) * 1000, DateTimeZone.UTC);
147 }
catch (SQLException ex) {
148 throw new TskCoreException(
"Error executing get spanning interval query: " + query, ex);
168 long start = timeRange.getStartMillis() / 1000;
169 long end = timeRange.getEndMillis() / 1000;
170 String sqlWhere = getSQLWhere(filter);
171 String augmentedEventsTablesSQL = getAugmentedEventsTablesSQL(filter);
172 String queryString =
" SELECT (SELECT Max(time) FROM " + augmentedEventsTablesSQL
173 +
" WHERE time <=" + start +
" AND " + sqlWhere +
") AS start,"
174 +
" (SELECT Min(time) FROM " + augmentedEventsTablesSQL
175 +
" WHERE time >= " + end +
" AND " + sqlWhere +
") AS end";
177 try (CaseDbConnection con = caseDB.getConnection();
178 Statement stmt = con.createStatement();
179 ResultSet results = stmt.executeQuery(queryString);) {
181 if (results.next()) {
182 long start2 = results.getLong(
"start");
183 long end2 = results.getLong(
"end");
188 return new Interval(start2 * 1000, (end2 + 1) * 1000, timeZone);
190 }
catch (SQLException ex) {
208 String sql =
"SELECT * FROM " + getAugmentedEventsTablesSQL(
false) +
" WHERE event_id = " + eventID;
210 try (CaseDbConnection con = caseDB.getConnection();
211 Statement stmt = con.createStatement();) {
212 try (ResultSet results = stmt.executeQuery(sql);) {
213 if (results.next()) {
214 int typeID = results.getInt(
"event_type_id");
217 results.getLong(
"data_source_obj_id"),
218 results.getLong(
"content_obj_id"),
219 results.getLong(
"artifact_id"),
220 results.getLong(
"time"),
221 type, results.getString(
"full_description"),
222 results.getString(
"med_description"),
223 results.getString(
"short_description"),
224 intToBoolean(results.getInt(
"hash_hit")),
225 intToBoolean(results.getInt(
"tagged")));
228 }
catch (SQLException sqlEx) {
248 Long startTime = timeRange.getStartMillis() / 1000;
249 Long endTime = timeRange.getEndMillis() / 1000;
251 if (Objects.equals(startTime, endTime)) {
255 ArrayList<Long> resultIDs =
new ArrayList<>();
257 String query =
"SELECT tsk_events.event_id AS event_id FROM " + getAugmentedEventsTablesSQL(filter)
258 +
" WHERE time >= " + startTime +
" AND time <" + endTime +
" AND " + getSQLWhere(filter) +
" ORDER BY time ASC";
260 try (CaseDbConnection con = caseDB.getConnection();
261 Statement stmt = con.createStatement();
262 ResultSet results = stmt.executeQuery(query);) {
263 while (results.next()) {
264 resultIDs.add(results.getLong(
"event_id"));
267 }
catch (SQLException sqlEx) {
268 throw new TskCoreException(
"Error while executing query " + query, sqlEx);
286 try (CaseDbConnection con = caseDB.getConnection();
287 Statement stms = con.createStatement();
288 ResultSet results = stms.executeQuery(STATEMENTS.GET_MAX_TIME.getSQL());) {
289 if (results.next()) {
290 return results.getLong(
"max");
292 }
catch (SQLException ex) {
293 throw new TskCoreException(
"Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex);
310 try (CaseDbConnection con = caseDB.getConnection();
311 Statement stms = con.createStatement();
312 ResultSet results = stms.executeQuery(STATEMENTS.GET_MIN_TIME.getSQL());) {
313 if (results.next()) {
314 return results.getLong(
"min");
316 }
catch (SQLException ex) {
317 throw new TskCoreException(
"Error while executing query " + STATEMENTS.GET_MAX_TIME.getSQL(), ex);
333 return Optional.ofNullable(eventTypeIDMap.get(eventTypeID));
342 return ImmutableList.copyOf(eventTypeIDMap.values());
345 private String insertOrIgnore(String query) {
348 return " INSERT " + query +
" ON CONFLICT DO NOTHING ";
350 return " INSERT OR IGNORE " + query;
352 throw new UnsupportedOperationException(
"Unsupported DB type: " + caseDB.
getDatabaseType().name());
359 private enum STATEMENTS {
361 GET_MAX_TIME(
"SELECT Max(time) AS max FROM tsk_events"),
362 GET_MIN_TIME(
"SELECT Min(time) AS min FROM tsk_events");
364 private final String sql;
366 private STATEMENTS(String sql) {
386 ArrayList<Long> eventIDs =
new ArrayList<>();
389 =
"SELECT event_id FROM tsk_events "
390 +
" LEFT JOIN tsk_event_descriptions on ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id ) "
391 +
" WHERE artifact_id = " + artifact.getArtifactID();
393 try (CaseDbConnection con = caseDB.getConnection();
394 Statement stmt = con.createStatement();
395 ResultSet results = stmt.executeQuery(query);) {
396 while (results.next()) {
397 eventIDs.add(results.getLong(
"event_id"));
399 }
catch (SQLException ex) {
400 throw new TskCoreException(
"Error executing getEventIDsForArtifact query.", ex);
422 try (CaseDbConnection conn = caseDB.getConnection()) {
423 return getEventAndDescriptionIDs(conn, content.getId(), includeDerivedArtifacts).keySet();
446 private long addEventDescription(
long dataSourceObjId,
long fileObjId, Long artifactID,
447 String fullDescription, String medDescription, String shortDescription,
448 boolean hasHashHits,
boolean tagged, CaseDbConnection connection)
throws TskCoreException {
449 String insertDescriptionSql
450 =
"INSERT INTO tsk_event_descriptions ( "
451 +
"data_source_obj_id, content_obj_id, artifact_id, "
452 +
" full_description, med_description, short_description, "
453 +
" hash_hit, tagged "
455 + dataSourceObjId +
","
457 + Objects.toString(artifactID,
"NULL") +
","
458 + quotePreservingNull(fullDescription) +
","
459 + quotePreservingNull(medDescription) +
","
460 + quotePreservingNull(shortDescription) +
", "
461 + booleanToInt(hasHashHits) +
","
462 + booleanToInt(tagged)
466 try (Statement insertDescriptionStmt = connection.createStatement()) {
467 connection.executeUpdate(insertDescriptionStmt, insertDescriptionSql, PreparedStatement.RETURN_GENERATED_KEYS);
468 try (ResultSet generatedKeys = insertDescriptionStmt.getGeneratedKeys()) {
469 generatedKeys.next();
470 return generatedKeys.getLong(1);
472 }
catch (SQLException ex) {
473 throw new TskCoreException(
"Failed to insert event description.", ex);
479 Collection<TimelineEvent> addEventsForNewFile(AbstractFile file, CaseDbConnection connection)
throws TskCoreException {
481 Map<TimelineEventType, Long> timeMap = ImmutableMap.of(TimelineEventType.FILE_CREATED, file.getCrtime(),
482 TimelineEventType.FILE_ACCESSED, file.getAtime(),
483 TimelineEventType.FILE_CHANGED, file.getCtime(),
484 TimelineEventType.FILE_MODIFIED, file.getMtime());
490 if (Collections.max(timeMap.values()) <= 0) {
491 return Collections.emptySet();
494 String description = file.getParentPath() + file.getName();
495 long fileObjId = file.getId();
496 Set<TimelineEvent> events =
new HashSet<>();
499 long descriptionID = addEventDescription(file.getDataSourceObjectId(), fileObjId, null,
500 description, null, null,
false,
false, connection);
502 for (Map.Entry<TimelineEventType, Long> timeEntry : timeMap.entrySet()) {
503 Long time = timeEntry.getValue();
505 TimelineEventType type = timeEntry.getKey();
506 long eventID = addEventWithExistingDescription(time, type, descriptionID, connection);
513 events.add(
new TimelineEvent(eventID, descriptionID, fileObjId, null, time, type,
514 description, null, null,
false,
false));
522 .map(TimelineEventAddedEvent::new)
523 .forEach(caseDB::fireTSKEvent);
541 Set<TimelineEvent> addArtifactEvents(BlackboardArtifact artifact)
throws TskCoreException {
542 Set<TimelineEvent> newEvents =
new HashSet<>();
549 if (artifact.getArtifactTypeID() == TSK_TL_EVENT.getTypeID()) {
550 TimelineEventType eventType;
551 BlackboardAttribute attribute = artifact.getAttribute(
new BlackboardAttribute.Type(TSK_TL_EVENT_TYPE));
552 if (attribute == null) {
553 eventType = TimelineEventType.OTHER;
555 long eventTypeID = attribute.getValueLong();
556 eventType = eventTypeIDMap.getOrDefault(eventTypeID, TimelineEventType.OTHER);
560 addArtifactEvent(((TimelineEventArtifactTypeImpl) TimelineEventType.OTHER)::makeEventDescription, eventType, artifact)
561 .ifPresent(newEvents::add);
567 Set<TimelineEventArtifactTypeImpl> eventTypesForArtifact = eventTypeIDMap.values().stream()
568 .filter(TimelineEventArtifactTypeImpl.class::isInstance)
569 .map(TimelineEventArtifactTypeImpl.class::cast)
570 .filter(eventType -> eventType.getArtifactTypeID() == artifact.getArtifactTypeID())
571 .collect(Collectors.toSet());
573 for (TimelineEventArtifactTypeImpl eventType : eventTypesForArtifact) {
574 addArtifactEvent(eventType::makeEventDescription, eventType, artifact)
575 .ifPresent(newEvents::add);
579 .map(TimelineEventAddedEvent::new)
580 .forEach(caseDB::fireTSKEvent);
601 private Optional<TimelineEvent> addArtifactEvent(TSKCoreCheckedFunction<BlackboardArtifact, TimelineEventDescriptionWithTime> payloadExtractor,
602 TimelineEventType eventType, BlackboardArtifact artifact)
throws TskCoreException {
603 TimelineEventDescriptionWithTime eventPayload = payloadExtractor.apply(artifact);
604 if (eventPayload == null) {
605 return Optional.empty();
607 long time = eventPayload.getTime();
610 return Optional.empty();
612 String fullDescription = eventPayload.getDescription(TimelineLevelOfDetail.HIGH);
613 String medDescription = eventPayload.getDescription(TimelineLevelOfDetail.MEDIUM);
614 String shortDescription = eventPayload.getDescription(TimelineLevelOfDetail.LOW);
615 long artifactID = artifact.getArtifactID();
616 long fileObjId = artifact.getObjectID();
617 long dataSourceObjectID = artifact.getDataSourceObjectID();
620 boolean hasHashHits =
false;
623 hasHashHits = isNotEmpty(file.getHashSetNames());
629 try (CaseDbConnection connection = caseDB.getConnection();) {
631 long descriptionID = addEventDescription(dataSourceObjectID, fileObjId, artifactID,
632 fullDescription, medDescription, shortDescription,
633 hasHashHits, tagged, connection);
635 long eventID = addEventWithExistingDescription(time, eventType, descriptionID, connection);
637 event =
new TimelineEvent(eventID, dataSourceObjectID, fileObjId, artifactID,
638 time, eventType, fullDescription, medDescription, shortDescription,
639 hasHashHits, tagged);
644 return Optional.of(event);
647 private long addEventWithExistingDescription(Long time, TimelineEventType type,
long descriptionID, CaseDbConnection connection)
throws TskCoreException {
648 String insertEventSql
649 =
"INSERT INTO tsk_events ( event_type_id, event_description_id , time) "
650 +
" VALUES (" + type.getTypeID() +
", " + descriptionID +
", " + time +
")";
653 try (Statement insertRowStmt = connection.createStatement();) {
654 connection.executeUpdate(insertRowStmt, insertEventSql, PreparedStatement.RETURN_GENERATED_KEYS);
656 try (ResultSet generatedKeys = insertRowStmt.getGeneratedKeys();) {
657 generatedKeys.next();
658 return generatedKeys.getLong(1);
660 }
catch (SQLException ex) {
661 throw new TskCoreException(
"Failed to insert event for existing description.", ex);
667 static private String quotePreservingNull(String value) {
668 return isNull(value) ?
" NULL " :
"'" + escapeSingleQuotes(value) +
"'";
671 private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn,
long contentObjID,
boolean includeArtifacts)
throws TskCoreException {
672 return getEventAndDescriptionIDsHelper(conn, contentObjID, (includeArtifacts ?
"" :
" AND artifact_id IS NULL"));
675 private Map<Long, Long> getEventAndDescriptionIDs(CaseDbConnection conn,
long contentObjID, Long artifactID)
throws TskCoreException {
676 return getEventAndDescriptionIDsHelper(conn, contentObjID,
" AND artifact_id = " + artifactID);
679 private Map<Long, Long> getEventAndDescriptionIDsHelper(CaseDbConnection con,
long fileObjID, String artifactClause)
throws TskCoreException {
681 Map<Long, Long> eventIDToDescriptionIDs =
new HashMap<>();
682 String sql =
"SELECT event_id, tsk_events.event_description_id"
683 +
" FROM tsk_events "
684 +
" LEFT JOIN tsk_event_descriptions ON ( tsk_events.event_description_id = tsk_event_descriptions.event_description_id )"
685 +
" WHERE content_obj_id = " + fileObjID
687 try (Statement selectStmt = con.createStatement(); ResultSet executeQuery = selectStmt.executeQuery(sql);) {
688 while (executeQuery.next()) {
689 eventIDToDescriptionIDs.put(executeQuery.getLong(
"event_id"), executeQuery.getLong(
"event_description_id"));
691 }
catch (SQLException ex) {
692 throw new TskCoreException(
"Error getting event description ids for object id = " + fileObjID, ex);
694 return eventIDToDescriptionIDs;
716 try (CaseDbConnection conn = caseDB.getConnection()) {
717 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(),
false);
718 updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
719 return eventIDs.keySet();
745 try (CaseDbConnection conn = caseDB.getConnection()) {
747 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, content.getId(),
false);
748 updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
749 return eventIDs.keySet();
751 return Collections.emptySet();
771 try (CaseDbConnection conn = caseDB.getConnection()) {
772 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
773 updateEventSourceTaggedFlag(conn, eventIDs.values(), 1);
774 return eventIDs.keySet();
794 try (CaseDbConnection conn = caseDB.getConnection()) {
796 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(conn, artifact.getObjectID(), artifact.getArtifactID());
797 updateEventSourceTaggedFlag(conn, eventIDs.values(), 0);
798 return eventIDs.keySet();
800 return Collections.emptySet();
807 private void updateEventSourceTaggedFlag(CaseDbConnection conn, Collection<Long> eventDescriptionIDs,
int flagValue)
throws TskCoreException {
808 if (eventDescriptionIDs.isEmpty()) {
812 String sql =
"UPDATE tsk_event_descriptions SET tagged = " + flagValue +
" WHERE event_description_id IN (" + buildCSVString(eventDescriptionIDs) +
")";
813 try (Statement updateStatement = conn.createStatement()) {
814 updateStatement.executeUpdate(sql);
815 }
catch (SQLException ex) {
816 throw new TskCoreException(
"Error marking content events tagged: " + sql, ex);
836 try (CaseDbConnection con = caseDB.getConnection(); Statement updateStatement = con.createStatement();) {
837 Map<Long, Long> eventIDs = getEventAndDescriptionIDs(con, content.getId(),
true);
838 if (! eventIDs.isEmpty()) {
839 String sql =
"UPDATE tsk_event_descriptions SET hash_hit = 1" +
" WHERE event_description_id IN (" + buildCSVString(eventIDs.values()) +
")";
841 updateStatement.executeUpdate(sql);
842 return eventIDs.keySet();
843 }
catch (SQLException ex) {
847 return eventIDs.keySet();
849 }
catch (SQLException ex) {
880 long adjustedEndTime = Objects.equals(startTime, endTime) ? endTime + 1 : endTime;
884 String queryString =
"SELECT count(DISTINCT tsk_events.event_id) AS count, " + typeColumn
885 +
" FROM " + getAugmentedEventsTablesSQL(filter)
886 +
" WHERE time >= " + startTime +
" AND time < " + adjustedEndTime +
" AND " + getSQLWhere(filter)
887 +
" GROUP BY " + typeColumn;
890 try (CaseDbConnection con = caseDB.getConnection();
891 Statement stmt = con.createStatement();
892 ResultSet results = stmt.executeQuery(queryString);) {
893 Map<TimelineEventType, Long> typeMap =
new HashMap<>();
894 while (results.next()) {
895 int eventTypeID = results.getInt(typeColumn);
897 .orElseThrow(() -> newEventTypeMappingException(eventTypeID));
899 typeMap.put(eventType, results.getLong(
"count"));
902 }
catch (SQLException ex) {
903 throw new TskCoreException(
"Error getting count of events from db: " + queryString, ex);
909 private static TskCoreException newEventTypeMappingException(
int eventTypeID) {
910 return new TskCoreException(
"Error mapping event type id " + eventTypeID +
" to EventType.");
926 static private String getAugmentedEventsTablesSQL(TimelineFilter.RootFilter filter) {
927 TimelineFilter.FileTypesFilter fileTypesFitler = filter.getFileTypesFilter();
928 boolean needsMimeTypes = fileTypesFitler != null && fileTypesFitler.hasSubFilters();
930 return getAugmentedEventsTablesSQL(needsMimeTypes);
947 static private String getAugmentedEventsTablesSQL(
boolean needMimeTypes) {
960 return "( SELECT event_id, time, tsk_event_descriptions.data_source_obj_id, content_obj_id, artifact_id, "
961 +
" full_description, med_description, short_description, tsk_events.event_type_id, super_type_id,"
962 +
" hash_hit, tagged "
963 + (needMimeTypes ?
", mime_type" :
"")
964 +
" FROM tsk_events "
965 +
" JOIN tsk_event_descriptions ON ( tsk_event_descriptions.event_description_id = tsk_events.event_description_id)"
966 +
" JOIN tsk_event_types ON (tsk_events.event_type_id = tsk_event_types.event_type_id ) "
967 + (needMimeTypes ?
" LEFT OUTER JOIN tsk_files "
968 +
" ON (tsk_event_descriptions.content_obj_id = tsk_files.obj_id)"
980 private static int booleanToInt(
boolean value) {
981 return value ? 1 : 0;
984 private static boolean intToBoolean(
int value) {
1001 List<TimelineEvent> events =
new ArrayList<>();
1003 Long startTime = timeRange.getStartMillis() / 1000;
1004 Long endTime = timeRange.getEndMillis() / 1000;
1006 if (Objects.equals(startTime, endTime)) {
1010 if (filter == null) {
1014 if (endTime < startTime) {
1019 String querySql =
"SELECT time, content_obj_id, data_source_obj_id, artifact_id, "
1023 +
" event_type_id, super_type_id, "
1024 +
" full_description, med_description, short_description "
1025 +
" FROM " + getAugmentedEventsTablesSQL(filter)
1026 +
" WHERE time >= " + startTime +
" AND time < " + endTime +
" AND " + getSQLWhere(filter)
1030 try (CaseDbConnection con = caseDB.getConnection();
1031 Statement stmt = con.createStatement();
1032 ResultSet resultSet = stmt.executeQuery(querySql);) {
1034 while (resultSet.next()) {
1035 int eventTypeID = resultSet.getInt(
"event_type_id");
1037 ->
new TskCoreException(
"Error mapping event type id " + eventTypeID +
"to EventType."));
1040 resultSet.getLong(
"event_id"),
1041 resultSet.getLong(
"data_source_obj_id"),
1042 resultSet.getLong(
"content_obj_id"),
1043 resultSet.getLong(
"artifact_id"),
1044 resultSet.getLong(
"time"),
1046 resultSet.getString(
"full_description"),
1047 resultSet.getString(
"med_description"),
1048 resultSet.getString(
"short_description"),
1049 resultSet.getInt(
"hash_hit") != 0,
1050 resultSet.getInt(
"tagged") != 0);
1055 }
catch (SQLException ex) {
1056 throw new TskCoreException(
"Error getting events from db: " + querySql, ex);
1071 private static String typeColumnHelper(
final boolean useSubTypes) {
1072 return useSubTypes ?
"event_type_id" :
"super_type_id";
1083 String getSQLWhere(TimelineFilter.RootFilter filter) {
1086 if (filter == null) {
1087 return getTrueLiteral();
1089 result = filter.getSQLWhere(
this);
1095 private String getTrueLiteral() {
1102 throw new UnsupportedOperationException(
"Unsupported DB type: " + caseDB.
getDatabaseType().name());
1120 this.addedEvent = event;
1131 @FunctionalInterface
1132 private interface TSKCoreCheckedFunction<I, O> {
1134 O apply(I input)
throws TskCoreException;
List< Long > getEventIDs(Interval timeRange, TimelineFilter.RootFilter filter)
TimelineEvent getAddedEvent()
TimelineEvent getEventById(long eventID)
ImmutableList< TimelineEventType > getEventTypes()
Interval getSpanningInterval(Interval timeRange, TimelineFilter.RootFilter filter, DateTimeZone timeZone)
Set< Long > getEventIDsForContent(Content content, boolean includeDerivedArtifacts)
TimelineEventType FILE_ACCESSED
Interval getSpanningInterval(Collection< Long > eventIDs)
Set< Long > updateEventsForContentTagAdded(Content content)
List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)
SortedSet<?extends TimelineEventType > getChildren()
Set< Long > updateEventsForContentTagDeleted(Content content)
Set< Long > updateEventsForHashSetHit(Content content)
TimelineEventType WEB_ACTIVITY
AbstractFile getAbstractFileById(long id)
TimelineEventType FILE_MODIFIED
void releaseSingleUserCaseReadLock()
TimelineEventType MISC_TYPES
static String escapeSingleQuotes(String text)
Set< Long > updateEventsForArtifactTagDeleted(BlackboardArtifact artifact)
void acquireSingleUserCaseWriteLock()
void releaseSingleUserCaseWriteLock()
TimelineEventType FILE_CREATED
TimelineEventType FILE_SYSTEM
Map< TimelineEventType, Long > countEventsByType(Long startTime, Long endTime, TimelineFilter.RootFilter filter, TimelineEventType.HierarchyLevel typeHierachyLevel)
List< Long > getEventIDsForArtifact(BlackboardArtifact artifact)
TimelineEventType ROOT_EVENT_TYPE
List< TimelineEvent > getEvents(Interval timeRange, TimelineFilter.RootFilter filter)
TimelineEventType CUSTOM_TYPES
void acquireSingleUserCaseReadLock()
List< ContentTag > getContentTagsByContent(Content content)
Optional< TimelineEventType > getEventType(long eventTypeID)
TimelineEventType FILE_CHANGED
Set< Long > updateEventsForArtifactTagAdded(BlackboardArtifact artifact)