diff --git a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
old mode 100644
new mode 100755
index 6df67e88659a184ef3d82dc39da5ab9b410e7bc5..3f029997c6c843ccc1453b86f5294f48c00fd5cf
--- a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
@@ -139,7 +139,7 @@ public BlackboardArtifact.Type getOrAddArtifactType(String typeName, String disp
 		if (category == null) {
 			throw new BlackboardException("Category provided must be non-null");
 		}
-		
+
 		try {
 			return caseDb.addBlackboardArtifactType(typeName, displayName, category);
 		} catch (TskDataException typeExistsEx) {
@@ -356,7 +356,6 @@ private Score deleteAnalysisResult(AnalysisResult analysisResult, CaseDbTransact
 			+ " WHERE arts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() //NON-NLS
 			+ "     AND types.category_type = " + BlackboardArtifact.Category.ANALYSIS_RESULT.getID(); // NON-NLS
 
-	
 	/**
 	 * Get all analysis results of given artifact type.
 	 *
@@ -374,9 +373,9 @@ public List<AnalysisResult> getAnalysisResultsByType(int artifactTypeId) throws
 	/**
 	 * Get all analysis results of given artifact type.
 	 *
-	 * @param artifactTypeId The artifact type id for which to search.
+	 * @param artifactTypeId  The artifact type id for which to search.
 	 * @param dataSourceObjId Object Id of the data source to look under.
-	 * 
+	 *
 	 * @return The list of analysis results.
 	 *
 	 * @throws TskCoreException Exception thrown if a critical error occurs
@@ -386,7 +385,6 @@ public List<AnalysisResult> getAnalysisResultsByType(int artifactTypeId, long da
 		return getAnalysisResultsWhere(" arts.artifact_type_id = " + artifactTypeId + " AND arts.data_source_obj_id = " + dataSourceObjId);
 	}
 
-	
 	/**
 	 * Get all analysis results for a given object.
 	 *
@@ -400,8 +398,7 @@ public List<AnalysisResult> getAnalysisResultsByType(int artifactTypeId, long da
 	public List<AnalysisResult> getAnalysisResults(long sourceObjId) throws TskCoreException {
 		return getAnalysisResultsWhere(" arts.obj_id = " + sourceObjId);
 	}
-	
-	
+
 	/**
 	 * Get all data artifacts for a given object.
 	 *
@@ -420,43 +417,53 @@ List<DataArtifact> getDataArtifactsBySource(long sourceObjId) throws TskCoreExce
 			caseDb.releaseSingleUserCaseReadLock();
 		}
 	}
-	
-	
+
 	/**
 	 * Returns true if there are data artifacts belonging to the sourceObjId.
+	 *
 	 * @param sourceObjId The source content object id.
+	 *
 	 * @return True if there are data artifacts belonging to this source obj id.
-	 * @throws TskCoreException 
+	 *
+	 * @throws TskCoreException
 	 */
 	public boolean hasDataArtifacts(long sourceObjId) throws TskCoreException {
 		return hasArtifactsOfCategory(BlackboardArtifact.Category.DATA_ARTIFACT, sourceObjId);
 	}
-	
+
 	/**
 	 * Returns true if there are analysis results belonging to the sourceObjId.
+	 *
 	 * @param sourceObjId The source content object id.
-	 * @return True if there are analysis results belonging to this source obj id.
-	 * @throws TskCoreException 
+	 *
+	 * @return True if there are analysis results belonging to this source obj
+	 *         id.
+	 *
+	 * @throws TskCoreException
 	 */
 	public boolean hasAnalysisResults(long sourceObjId) throws TskCoreException {
 		return hasArtifactsOfCategory(BlackboardArtifact.Category.ANALYSIS_RESULT, sourceObjId);
 	}
-	
-	
+
 	/**
-	 * Returns true if there are artifacts of the given category belonging to the sourceObjId.
-	 * @param category The category of the artifacts.
+	 * Returns true if there are artifacts of the given category belonging to
+	 * the sourceObjId.
+	 *
+	 * @param category    The category of the artifacts.
 	 * @param sourceObjId The source content object id.
-	 * @return True if there are artifacts of the given category belonging to this source obj id.
-	 * @throws TskCoreException 
+	 *
+	 * @return True if there are artifacts of the given category belonging to
+	 *         this source obj id.
+	 *
+	 * @throws TskCoreException
 	 */
 	private boolean hasArtifactsOfCategory(BlackboardArtifact.Category category, long sourceObjId) throws TskCoreException {
 		String queryString = "SELECT COUNT(*) AS count " //NON-NLS
-			+ " FROM blackboard_artifacts AS arts "
-			+ " JOIN blackboard_artifact_types AS types " //NON-NLS
-			+ "		ON arts.artifact_type_id = types.artifact_type_id" //NON-NLS
-			+ " WHERE types.category_type = " + category.getID()
-			+ " AND arts.obj_id = " + sourceObjId;
+				+ " FROM blackboard_artifacts AS arts "
+				+ " JOIN blackboard_artifact_types AS types " //NON-NLS
+				+ "		ON arts.artifact_type_id = types.artifact_type_id" //NON-NLS
+				+ " WHERE types.category_type = " + category.getID()
+				+ " AND arts.obj_id = " + sourceObjId;
 
 		caseDb.acquireSingleUserCaseReadLock();
 		try (SleuthkitCase.CaseDbConnection connection = caseDb.getConnection();
@@ -473,9 +480,6 @@ private boolean hasArtifactsOfCategory(BlackboardArtifact.Category category, lon
 		}
 	}
 
-
-	
-	
 	/**
 	 * Get all analysis results for a given object.
 	 *
@@ -630,6 +634,31 @@ private List<AnalysisResult> resultSetToAnalysisResults(ResultSet resultSet) thr
 			+ " WHERE artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() //NON-NLS
 			+ "     AND types.category_type = " + BlackboardArtifact.Category.DATA_ARTIFACT.getID(); // NON-NLS
 
+	/**
+	 * Gets all data artifacts of a given type for a given data source. To get
+	 * all the data artifacts for the data source, pass null for the type ID.
+	 *
+	 * @param dataSourceObjId The object ID of the data source.
+	 * @param artifactTypeID  The type ID of the desired artifacts or null.
+	 *
+	 * @return A list of the data artifacts, possibly empty.
+	 *
+	 * @throws TskCoreException This exception is thrown if there is an error
+	 *                          querying the case database.
+	 */
+	public List<DataArtifact> getDataArtifacts(long dataSourceObjId, Integer artifactTypeID) throws TskCoreException {
+		caseDb.acquireSingleUserCaseReadLock();
+		try (CaseDbConnection connection = caseDb.getConnection()) {
+			String whereClause = " artifacts.data_source_obj_id = " + dataSourceObjId;
+			if (artifactTypeID != null) {
+				whereClause += " AND artifacts.artifact_type_id = " + artifactTypeID;
+			}
+			return getDataArtifactsWhere(whereClause, connection);
+		} finally {
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}
+
 	/**
 	 * Get all data artifacts of a given type for a given data source.
 	 *
@@ -786,14 +815,14 @@ private List<DataArtifact> resultSetToDataArtifacts(ResultSet resultSet, CaseDbC
 	 *
 	 * @return The artifact type.
 	 *
-	 * @throws TskCoreException If an error occurs accessing the case database 
-	 *						    or no value is found.
+	 * @throws TskCoreException If an error occurs accessing the case database
+	 *                          or no value is found.
 	 *
 	 */
 	public BlackboardArtifact.Type getArtifactType(int artTypeId) throws TskCoreException {
 		return caseDb.getArtifactType(artTypeId);
 	}
-	
+
 	/**
 	 * Gets an attribute type, creating it if it does not already exist. Use
 	 * this method to define custom attribute types.
@@ -852,7 +881,7 @@ public List<BlackboardArtifact.Type> getArtifactTypesInUse(long dataSourceObjId)
 			List<BlackboardArtifact.Type> uniqueArtifactTypes = new ArrayList<>();
 			while (resultSet.next()) {
 				uniqueArtifactTypes.add(new BlackboardArtifact.Type(resultSet.getInt("artifact_type_id"),
-						resultSet.getString("type_name"), resultSet.getString("display_name"), 
+						resultSet.getString("type_name"), resultSet.getString("display_name"),
 						BlackboardArtifact.Category.fromID(resultSet.getInt("category_type"))));
 			}
 			return uniqueArtifactTypes;
@@ -1098,7 +1127,6 @@ private boolean attributesMatch(Collection<BlackboardAttribute> fileAttributesLi
 
 	}
 
-
 	/**
 	 * A Blackboard exception.
 	 */
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
index 08f80085abc599e1a553eece27e8e4a5c1119486..6a52a5d8b40a1cd2aa94183251e69c4f62ae45f7 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
+++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties
@@ -312,7 +312,9 @@ IngestJobInfo.IngestJobStatusType.Started.displayName=Started
 IngestJobInfo.IngestJobStatusType.Cancelled.displayName=Cancelled
 IngestJobInfo.IngestJobStatusType.Completed.displayName=Completed
 IngestModuleInfo.IngestModuleType.FileLevel.displayName=File Level
+IngestModuleInfo.IngestModuleType.DataArtifact.displayName=Data Artifact
 IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName=Data Source Level
+IngestModuleInfo.IngestModuleType.Multiple.displayName=Multiple
 ReviewStatus.Approved=Approved
 ReviewStatus.Rejected=Rejected
 ReviewStatus.Undecided=Undecided
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
index 08f80085abc599e1a553eece27e8e4a5c1119486..6a52a5d8b40a1cd2aa94183251e69c4f62ae45f7 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
+++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
@@ -312,7 +312,9 @@ IngestJobInfo.IngestJobStatusType.Started.displayName=Started
 IngestJobInfo.IngestJobStatusType.Cancelled.displayName=Cancelled
 IngestJobInfo.IngestJobStatusType.Completed.displayName=Completed
 IngestModuleInfo.IngestModuleType.FileLevel.displayName=File Level
+IngestModuleInfo.IngestModuleType.DataArtifact.displayName=Data Artifact
 IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName=Data Source Level
+IngestModuleInfo.IngestModuleType.Multiple.displayName=Multiple
 ReviewStatus.Approved=Approved
 ReviewStatus.Rejected=Rejected
 ReviewStatus.Undecided=Undecided
diff --git a/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java b/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java
index bec082446464559c1b1c4b89aababacb46e3f919..ef99ac17e393f23e261b4f8ab1c67296355dcc96 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/IngestModuleInfo.java
@@ -1,7 +1,7 @@
 /*
  * Sleuth Kit Data Model
  *
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2014-2021 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@
 import java.util.ResourceBundle;
 
 /**
- * Class representing information about an ingest module, used in ingest job
+ * Represents information about an ingest module factory, used in ingest job
  * info to show which ingest modules were run.
  */
 public final class IngestModuleInfo {
@@ -32,12 +32,17 @@ public final class IngestModuleInfo {
 	 * Used to keep track of the module types
 	 */
 	public static enum IngestModuleType {
-		//DO NOT CHANGE ORDER
+		/*
+		 * IMPORTANT: DO NOT CHANGE ORDER, THE ORDINAL VALUES OF THE ENUM ARE
+		 * STORED IN THE CASE DATABASE
+		 */
 		DATA_SOURCE_LEVEL(bundle.getString("IngestModuleInfo.IngestModuleType.DataSourceLevel.displayName")),
-		FILE_LEVEL(bundle.getString("IngestModuleInfo.IngestModuleType.FileLevel.displayName"));
-		
-		private String displayName;
-		
+		FILE_LEVEL(bundle.getString("IngestModuleInfo.IngestModuleType.FileLevel.displayName")),
+		DATA_ARTIFACT(bundle.getString("IngestModuleInfo.IngestModuleType.DataArtifact.displayName")),
+		MULTIPLE("IngestModuleInfo.IngestModuleType.Multiple.displayName");
+
+		private final String displayName;
+
 		private IngestModuleType(String displayName) {
 			this.displayName = displayName;
 		}