diff --git a/bindings/java/doxygen/artifact_catalog.dox b/bindings/java/doxygen/artifact_catalog.dox index 1f50c7c1ed0ace2df7b38ff0a638458a02a092fd..d27ce4a1de69effa16db8514653793981222fb9d 100644 --- a/bindings/java/doxygen/artifact_catalog.dox +++ b/bindings/java/doxygen/artifact_catalog.dox @@ -422,7 +422,16 @@ General metadata for some content. ### REQUIRED ATTRIBUTES None - +### OPTIONAL ATTRIBUTES +- TSK_DATETIME_CREATED (Timestamp the document was created) +- TSK_DATETIME_MODIFIED (Timestamp the document was modified) +- TSK_DESCRIPTION (Title of the document) +- TSK_LAST_PRINTED_DATETIME (Timestamp when document was last printed) +- TSK_ORGANIZATION (Organization/Company who owns the document) +- TSK_OWNER (Author of the document) +- TSK_PROG_NAME (Program used to create the document) +- TSK_USER_ID (Last author of the document) +- TSK_VERSION (Version number of the program used to create the document) --- ## TSK_METADATA_EXIF diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp index 5dd64a30dd7c894c4e5e6688be7ff1de06d777e5..327d5d3602cbdcc938f6f2f52e1165136f8e4a74 100644 --- a/bindings/java/jni/auto_db_java.cpp +++ b/bindings/java/jni/auto_db_java.cpp @@ -84,11 +84,6 @@ TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) { return TSK_ERR; } - m_addImageNameMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageName", "(JLjava/lang/String;J)I"); - if (m_addImageNameMethodID == NULL) { - return TSK_ERR; - } - m_addVolumeSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVsInfo", "(JIJJ)J"); if (m_addVolumeSystemMethodID == NULL) { return TSK_ERR; @@ -227,29 +222,6 @@ TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const st return TSK_OK; } -/** -* Adds one image name -* @param objId The object ID of the image -* @param imgName The image name -* @param sequence The sequence number for this image name -* @returns TSK_ERR on error, TSK_OK on success -*/ -TSK_RETVAL_ENUM -TskAutoDbJava::addImageName(int64_t objId, char const* imgName, int sequence) { - - jstring imgNamej = m_jniEnv->NewStringUTF(imgName); - - jint res = m_jniEnv->CallIntMethod(m_javaDbObj, m_addImageNameMethodID, - objId, imgNamej, (int64_t)sequence); - - if (res == 0) { - return TSK_OK; - } - else { - return TSK_ERR; - } -} - /** * Adds volume system to database. Object ID for new vs stored in objId. * diff --git a/bindings/java/src/org/sleuthkit/datamodel/BlackboardAttribute.java b/bindings/java/src/org/sleuthkit/datamodel/BlackboardAttribute.java index fe9caf98ab67f00ceb6fcb6baef72173162a160f..0e9d449e28ccab2b4ccd05c739a118ff2c6cf3aa 100755 --- a/bindings/java/src/org/sleuthkit/datamodel/BlackboardAttribute.java +++ b/bindings/java/src/org/sleuthkit/datamodel/BlackboardAttribute.java @@ -1411,7 +1411,12 @@ public enum ATTRIBUTE_TYPE { TSK_BYTES_RECEIVED(148, "TSK_BYTES_RECEIVED", bundle.getString("BlackboardAttribute.tskbytesreceived.text"), - TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG) + TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG), + + TSK_LAST_PRINTED_DATETIME(149, "TSK_LAST_PRINTED_DATETIME", + bundle.getString("BlackboardAttribute.tsklastprinteddatetime.text"), + TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME), + ; diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties index 8e4eb394218156142eda7c22a938ef892ce76a31..16335ea7bdca9678b386461b6e9db36b52ef82ad 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties +++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties @@ -198,6 +198,7 @@ BlackboardAttribute.tskdistancefromhome.text=Distance from Homepoint BlackboardAttribute.tskhashphotodna.text=PhotoDNA Hash BlackboardAttribute.tskbytessent.text=Bytes Sent BlackboardAttribute.tskbytesreceived.text=Bytes Received +BlackboardAttribute.tsklastprinteddatetime.text=Last Printed Date AbstractFile.readLocal.exception.msg4.text=Error reading local file\: {0} AbstractFile.readLocal.exception.msg1.text=Error reading local file, local path is not set AbstractFile.readLocal.exception.msg2.text=Error reading local file, it does not exist at local path\: {0} @@ -323,6 +324,9 @@ MiscTypes.GPSBookmark.name=GPS Bookmark MiscTypes.GPSLastknown.name=GPS Last Known Location MiscTypes.GPSearch.name=GPS Search MiscTypes.GPSTrack.name=GPS Track +MiscTypes.metadataLastPrinted.name=Document Last Printed +MiscTypes.metadataLastSaved.name=Document Last Saved +MiscTypes.metadataCreated.name=Document Created RootEventType.eventTypes.name=Event Types WebTypes.webDownloads.name=Web Downloads WebTypes.webCookies.name=Web Cookies diff --git a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java index 8790b4bcaf3a3f29625489ba69ab318a2b3c1b6c..43256938eb117da8235ba6bed79571f74a2de43a 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java @@ -1291,6 +1291,64 @@ public List<Account.Type> getAccountTypesInUse() throws TskCoreException { } } + /** + * Gets a list of accounts that are related to the given artifact. + * + * @param artifact + * + * @return A list of distinct accounts or an empty list if none where found. + * + * @throws TskCoreException + */ + public List<Account> getAccountsRelatedToArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (artifact == null) { + throw new IllegalArgumentException("null arugment passed to getAccountsRelatedToArtifact"); + } + + List<Account> accountList = new ArrayList<>(); + try (CaseDbConnection connection = db.getConnection()) { + db.acquireSingleUserCaseReadLock(); + try { + // In order to get a list of all the unique accounts in a relationship with the given aritfact + // we must first union a list of the unique account1_id in the relationship with artifact + // then the unique account2_id (inner select with union). The outter select assures the list + // of the inner select only contains unique accounts. + String query = String.format("SELECT DISTINCT (account_id), account_type_id, account_unique_identifier" + + " FROM (" + + " SELECT DISTINCT (account_id), account_type_id, account_unique_identifier" + + " FROM accounts" + + " JOIN account_relationships ON account1_id = account_id" + + " WHERE relationship_source_obj_id = %d" + + " UNION " + + " SELECT DISTINCT (account_id), account_type_id, account_unique_identifier" + + " FROM accounts" + + " JOIN account_relationships ON account2_id = account_id" + + " WHERE relationship_source_obj_id = %d)", artifact.getId(), artifact.getId()); + try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(query)) { + while (rs.next()) { + Account.Type accountType = null; + int accountTypeId = rs.getInt("account_type_id"); + for (Map.Entry<Account.Type, Integer> entry : accountTypeToTypeIdMap.entrySet()) { + if (entry.getValue() == accountTypeId) { + accountType = entry.getKey(); + break; + } + } + + accountList.add(new Account(rs.getInt("account_id"), accountType, rs.getString("account_unique_identifier"))); + } + } catch (SQLException ex) { + throw new TskCoreException("Unable to get account list for give artifact " + artifact.getId(), ex); + } + + } finally { + db.releaseSingleUserCaseReadLock(); + } + } + + return accountList; + } + /** * Get account_type_id for the given account type. * @@ -1327,8 +1385,6 @@ private String normalizeAccountID(Account.Type accountType, String accountUnique return normailzeAccountID; } - - /** * Builds the SQL for the given CommunicationsFilter. * diff --git a/bindings/java/src/org/sleuthkit/datamodel/Image.java b/bindings/java/src/org/sleuthkit/datamodel/Image.java index 1b920333abeb0c7a6ab813d46f4c107e0b455b5c..a465af5975966e2f9cc12870732f1af7f1a3328c 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Image.java +++ b/bindings/java/src/org/sleuthkit/datamodel/Image.java @@ -259,8 +259,8 @@ public List<Volume> getVolumes() throws TskCoreException { * @throws TskCoreException */ public List<FileSystem> getFileSystems() throws TskCoreException { - List<FileSystem> fs = new ArrayList<FileSystem>(); - fs.addAll(getSleuthkitCase().getFileSystems(this)); + List<FileSystem> fs = new ArrayList<>(); + fs.addAll(getSleuthkitCase().getImageFileSystems(this)); return fs; } diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java index 5d61e57c7d66e18cf876584eb7f58e0b99c89e00..0422de29a3774ebc864b896ea03dd692a6696941 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java +++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java @@ -22,8 +22,10 @@ import java.util.List; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; @@ -33,6 +35,9 @@ * case database. All callbacks from the native code should come through this class. * Any changes to the method signatures in this class will require changes to the * native code. + * + * Note that this code should only be used for the add image process, and not + * to add additional files afterward. */ class JniDbHelper { @@ -47,8 +52,9 @@ class JniDbHelper { private final Map<ParentCacheKey, Long> parentDirCache = new HashMap<>(); private static final long BATCH_FILE_THRESHOLD = 500; - private final List<FileInfo> batchedFiles = new ArrayList<>(); - private final List<LayoutRangeInfo> batchedLayoutRanges = new ArrayList<>(); + private final Queue<FileInfo> batchedFiles = new LinkedList<>(); + private final Queue<LayoutRangeInfo> batchedLayoutRanges = new LinkedList<>(); + private final List<Long> layoutFileIds = new ArrayList<>(); JniDbHelper(SleuthkitCase caseDb, AddDataSourceCallbacks addDataSourceCallbacks) { this.caseDb = caseDb; @@ -95,6 +101,7 @@ private void revertTransaction() { void finish() { addBatchedFilesToDb(); addBatchedLayoutRangesToDb(); + processLayoutFiles(); } /** @@ -125,7 +132,13 @@ long addImageInfo(int type, long ssize, String timezone, } commitTransaction(); - addDataSourceCallbacks.onDataSourceAdded(objId); + try { + addDataSourceCallbacks.onDataSourceAdded(objId); + } catch (Exception ex) { + // Exception firewall - we do not want to return to the native code without + // passing it the data source ID + logger.log(Level.SEVERE, "Unexpected error from data source added callback", ex); + } return objId; } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding image to the database", ex); @@ -134,30 +147,6 @@ long addImageInfo(int type, long ssize, String timezone, } } - /** - * Add an image name to the database. - * Intended to be called from the native code during the add image process. - * - * @param objId The object id of the image. - * @param name The file name for the image - * @param sequence The sequence number of this file. - * - * @return 0 if successful, -1 if not - */ - int addImageName(long objId, String name, long sequence) { - try { - beginTransaction(); - caseDb.addImageNameJNI(objId, name, sequence, trans); - commitTransaction(); - return 0; - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error adding image name to the database - image obj ID: " + objId + ", image name: " + name - + ", sequence: " + sequence, ex); - revertTransaction(); - return -1; - } - } - /** * Add a volume system to the database. * Intended to be called from the native code during the add image process. @@ -348,7 +337,8 @@ private long addBatchedFilesToDb() { List<Long> newObjIds = new ArrayList<>(); try { beginTransaction(); - for (FileInfo fileInfo : batchedFiles) { + FileInfo fileInfo; + while ((fileInfo = batchedFiles.poll()) != null) { long computedParentObjId = fileInfo.parentObjId; try { // If we weren't given the parent object ID, look it up @@ -388,20 +378,20 @@ private long addBatchedFilesToDb() { } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + computedParentObjId + ", file system object ID: " + fileInfo.fsObjId + ", name: " + fileInfo.name, ex); - revertTransaction(); - batchedFiles.clear(); - return -1; } } commitTransaction(); - addDataSourceCallbacks.onFilesAdded(newObjIds); + try { + addDataSourceCallbacks.onFilesAdded(newObjIds); + } catch (Exception ex) { + // Exception firewall to prevent unexpected return to the native code + logger.log(Level.SEVERE, "Unexpected error from files added callback", ex); + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding batched files to database", ex); revertTransaction(); - batchedFiles.clear(); return -1; } - batchedFiles.clear(); return 0; } @@ -426,10 +416,11 @@ private long getParentObjId(FileInfo fileInfo) throws TskCoreException { if (parentDirCache.containsKey(key)) { return parentDirCache.get(key); } else { - // The parent wasn't found in the cache so do a database query - java.io.File parentAsFile = new java.io.File(parentPath); - return caseDb.findParentObjIdJNI(fileInfo.parMetaAddr, fileInfo.fsObjId, parentAsFile.getPath(), parentAsFile.getName(), trans); - } + // There's no reason to do a database query since every folder added is being + // stored in the cache. + throw new TskCoreException("Parent not found in cache (fsObjId: " +fileInfo.fsObjId + ", parMetaAddr: " + fileInfo.parMetaAddr + + ", parSeq: " + fileInfo.parSeq + ", parentPath: " + parentPath + ")"); + } } /** @@ -474,6 +465,10 @@ long addLayoutFile(long parentObjId, null, null, true, trans); commitTransaction(); + + // Store the layout file ID for later processing + layoutFileIds.add(objId); + return objId; } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding layout file to the database - parent object ID: " + parentObjId @@ -511,19 +506,16 @@ long addLayoutFileRange(long objId, long byteStart, long byteLen, long seq) { private long addBatchedLayoutRangesToDb() { try { beginTransaction(); - - for (LayoutRangeInfo range : batchedLayoutRanges) { + LayoutRangeInfo range; + while ((range = batchedLayoutRanges.poll()) != null) { try { caseDb.addLayoutFileRangeJNI(range.objId, range.byteStart, range.byteLen, range.seq, trans); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + range.objId + ", byte start: " + range.byteStart, ex); - revertTransaction(); - return -1; } } commitTransaction(); - batchedLayoutRanges.clear(); return 0; } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding batched files to database", ex); @@ -531,6 +523,16 @@ private long addBatchedLayoutRangesToDb() { return -1; } } + + /** + * Send completed layout files on for further processing. + * Note that this must wait until we know all the ranges for each + * file have been added to the database. + */ + void processLayoutFiles() { + addDataSourceCallbacks.onFilesAdded(layoutFileIds); + layoutFileIds.clear(); + } /** * Add a virtual directory to hold unallocated file system blocks. diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 4933d492aba8714ceff6cb5f27685e146b0f4f07..9d8002f52998a4b169db6a0e0c91926e458060db 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -10123,7 +10123,7 @@ public List<ContentTag> getContentTagsByContent(Content content) throws TskCoreE * row. * * @throws TskCoreException - * @Deprecated User TaggingManager.addArtifactTag instead. + * @deprecated User TaggingManager.addArtifactTag instead. */ @Deprecated public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { @@ -11074,44 +11074,6 @@ void addImageNameJNI(long objId, String name, long sequence, } } - /** - * Looks up a parent file object ID. The calling thread is expected to have - * a case read lock. For use with the JNI callbacks associated with the add - * image process. - * - * @param metaAddr The metadata address. - * @param fsObjId The file system object ID. - * @param path The file path. - * @param name The file name. - * @param transaction The open transaction. - * - * @return The object ID if found, -1 otherwise. - * - * @throws TskCoreException - */ - long findParentObjIdJNI(long metaAddr, long fsObjId, String path, String name, CaseDbTransaction transaction) throws TskCoreException { - ResultSet resultSet = null; - try { - CaseDbConnection connection = transaction.getConnection(); - PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_OBJ_ID_BY_META_ADDR_AND_PATH); - preparedStatement.clearParameters(); - preparedStatement.setLong(1, metaAddr); - preparedStatement.setLong(2, fsObjId); - preparedStatement.setString(3, path); - preparedStatement.setString(4, name); - resultSet = connection.executeQuery(preparedStatement); - if (resultSet.next()) { - return resultSet.getLong("obj_id"); - } else { - throw new TskCoreException(String.format("Error looking up parent - meta addr: %d, path: %s, name: %s", metaAddr, path, name)); - } - } catch (SQLException ex) { - throw new TskCoreException(String.format("Error looking up parent - meta addr: %d, path: %s, name: %s", metaAddr, path, name), ex); - } finally { - closeResultSet(resultSet); - } - } - /** * Add a file system file to the database. For use with the JNI callbacks * associated with the add image process. @@ -11535,7 +11497,6 @@ private enum PREPARED_STATEMENT { INSERT_POOL_INFO("INSERT INTO tsk_pool_info (obj_id, pool_type) VALUES (?, ?)"), INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), - SELECT_OBJ_ID_BY_META_ADDR_AND_PATH("SELECT obj_id FROM tsk_files WHERE meta_addr = ? AND fs_obj_id = ? AND parent_path = ? AND name = ?"), SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?"); private final String sql; diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java index a6fabdadb4ea950bbc5d30c045ace5498e4ede3b..8f99e5af32c36792f65d390f349776e0b42f0aab 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventType.java @@ -224,7 +224,7 @@ public int compare(TimelineEventType o1, TimelineEventType o2) { builder.add(CALL_LOG, DEVICES_ATTACHED, EMAIL, EXIF, GPS_BOOKMARK, GPS_LAST_KNOWN_LOCATION, GPS_TRACKPOINT, GPS_ROUTE, GPS_SEARCH, GPS_TRACK, INSTALLED_PROGRAM, LOG_ENTRY, MESSAGE, - RECENT_DOCUMENTS, REGISTRY); + METADATA_LAST_PRINTED, METADATA_LAST_SAVED, METADATA_CREATED, RECENT_DOCUMENTS, REGISTRY); return builder.build(); } @@ -526,7 +526,35 @@ public SortedSet< TimelineEventType> getChildren() { MISC_TYPES, new BlackboardArtifact.Type(TSK_GPS_TRACK), new Type(TSK_NAME)); + + TimelineEventType METADATA_LAST_PRINTED = new TimelineEventArtifactTypeImpl(33, + getBundle().getString("MiscTypes.metadataLastPrinted.name"),// NON-NLS + MISC_TYPES, + new BlackboardArtifact.Type(TSK_METADATA), + new BlackboardAttribute.Type(TSK_LAST_PRINTED_DATETIME), + artf -> {return getBundle().getString("MiscTypes.metadataLastPrinted.name");}, + new EmptyExtractor(), + new EmptyExtractor()); + + TimelineEventType METADATA_LAST_SAVED = new TimelineEventArtifactTypeImpl(34, + getBundle().getString("MiscTypes.metadataLastSaved.name"),// NON-NLS + MISC_TYPES, + new BlackboardArtifact.Type(TSK_METADATA), + new BlackboardAttribute.Type(TSK_DATETIME_MODIFIED), + artf -> {return getBundle().getString("MiscTypes.metadataLastSaved.name");}, + new EmptyExtractor(), + new EmptyExtractor()); + + TimelineEventType METADATA_CREATED = new TimelineEventArtifactTypeImpl(35, + getBundle().getString("MiscTypes.metadataCreated.name"),// NON-NLS + MISC_TYPES, + new BlackboardArtifact.Type(TSK_METADATA), + new BlackboardAttribute.Type(TSK_DATETIME_CREATED), + artf -> {return getBundle().getString("MiscTypes.metadataCreated.name");}, + new EmptyExtractor(), + new EmptyExtractor()); + static SortedSet<? extends TimelineEventType> getCategoryTypes() { return ROOT_EVENT_TYPE.getChildren(); } diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/GeoArtifactsHelper.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/GeoArtifactsHelper.java index 558fda9b7f66d63e807050840ece29fa3bb63e84..52c433a6a04435ffc0765cc714460906b2a7ceb6 100755 --- a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/GeoArtifactsHelper.java +++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/GeoArtifactsHelper.java @@ -37,7 +37,7 @@ public final class GeoArtifactsHelper extends ArtifactHelperBase { private static final BlackboardAttribute.Type WAYPOINTS_ATTR_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS); - private static final BlackboardAttribute.Type TRACKPOINTS_ATTR_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); + private static final BlackboardAttribute.Type TRACKPOINTS_ATTR_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); private final String programName; /** @@ -67,28 +67,32 @@ public GeoArtifactsHelper(SleuthkitCase caseDb, String moduleName, String progra * (elevation) axes. * * @param trackName The name of the GPS track, may be null. - * @param trackPoints The track points that make up the track. + * @param trackPoints The track points that make up the track. This list + * should be non-null and non-empty. * @param moreAttributes Additional attributes for the TSK_GPS_TRACK * artifact, may be null. * * @return The TSK_GPS_TRACK artifact that was added to the case database. * - * @throws TskCoreException If there is an error creating the artifact. - * @throws BlackboardException If there is a error posting the artifact to - * the blackboard. + * @throws TskCoreException If there is an error creating the + * artifact. + * @throws BlackboardException If there is a error posting the artifact + * to the blackboard. + * @throws IllegalArgumentException If the trackpoints provided are null or + * empty. */ public BlackboardArtifact addTrack(String trackName, GeoTrackPoints trackPoints, List<BlackboardAttribute> moreAttributes) throws TskCoreException, BlackboardException { - if (trackPoints == null) { - throw new IllegalArgumentException(String.format("addTrack was passed a null list of track points")); + if (trackPoints == null || trackPoints.isEmpty()) { + throw new IllegalArgumentException(String.format("addTrack was passed a null or empty list of track points")); } - BlackboardArtifact artifact = getContent().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK); List<BlackboardAttribute> attributes = new ArrayList<>(); if (trackName != null) { attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, getModuleName(), trackName)); } + // acquire necessary attribute. If 'toAttribute' call throws an exception, an artifact will not be created for this instance. attributes.add(BlackboardJsonAttrUtil.toAttribute(TRACKPOINTS_ATTR_TYPE, getModuleName(), trackPoints)); if (programName != null) { @@ -99,13 +103,14 @@ public BlackboardArtifact addTrack(String trackName, GeoTrackPoints trackPoints, attributes.addAll(moreAttributes); } + BlackboardArtifact artifact = getContent().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK); artifact.addAttributes(attributes); getSleuthkitCase().getBlackboard().postArtifact(artifact, getModuleName()); return artifact; - } - + } + /** * Adds a TSK_GPS_ROUTE artifact to the case database. A Global Positioning * System (GPS) route artifact records one or more waypoints entered into a @@ -117,19 +122,23 @@ public BlackboardArtifact addTrack(String trackName, GeoTrackPoints trackPoints, * @param creationTime The time at which the route was created as * milliseconds from the Java epoch of * 1970-01-01T00:00:00Z, may be null. - * @param wayPoints The waypoints that make up the route. + * @param wayPoints The waypoints that make up the route. This list + * should be non-null and non-empty. * @param moreAttributes Additional attributes for the TSK_GPS_ROUTE * artifact, may be null. * * @return The TSK_GPS_ROUTE artifact that was added to the case database. * - * @throws TskCoreException If there is an error creating the artifact. - * @throws BlackboardException If there is a error posting the artifact to - * the blackboard. + * @throws TskCoreException If there is an error creating the + * artifact. + * @throws BlackboardException If there is a error posting the artifact + * to the blackboard. + * @throws IllegalArgumentException If the waypoints provided are null or + * empty. */ public BlackboardArtifact addRoute(String routeName, Long creationTime, GeoWaypoints wayPoints, List<BlackboardAttribute> moreAttributes) throws TskCoreException, BlackboardException { - if (wayPoints == null) { - throw new IllegalArgumentException(String.format("addRoute was passed a null list of waypoints")); + if (wayPoints == null || wayPoints.isEmpty()) { + throw new IllegalArgumentException(String.format("addRoute was passed a null or empty list of waypoints")); } BlackboardArtifact artifact = getContent().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); @@ -159,5 +168,5 @@ public BlackboardArtifact addRoute(String routeName, Long creationTime, GeoWaypo return artifact; } - + }