From 625010b89c0b7d65f386d227c1a1aaa0e83a99d0 Mon Sep 17 00:00:00 2001
From: apriestman <apriestman@basistech.com>
Date: Thu, 21 Oct 2021 13:09:01 -0400
Subject: [PATCH] Move some artifact and attribute type methods to Blackboard

---
 .../sleuthkit/datamodel/AbstractContent.java  |   4 +-
 .../org/sleuthkit/datamodel/AbstractFile.java |   2 +-
 .../org/sleuthkit/datamodel/Blackboard.java   | 592 ++++++++++++++++--
 .../datamodel/BlackboardArtifact.java         |   8 +-
 .../datamodel/CommunicationsManager.java      |   4 +-
 .../sleuthkit/datamodel/OsAccountManager.java |   2 +-
 .../src/org/sleuthkit/datamodel/Report.java   |   4 +-
 .../sleuthkit/datamodel/SleuthkitCase.java    | 543 ++--------------
 8 files changed, 616 insertions(+), 543 deletions(-)

diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractContent.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractContent.java
index 9dd3dc02e..27778032d 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/AbstractContent.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractContent.java
@@ -327,7 +327,7 @@ public BlackboardArtifact newArtifact(int artifactTypeID) throws TskCoreExceptio
 		if (artifactTypeID == ARTIFACT_TYPE.TSK_GEN_INFO.getTypeID()) {
 			return getGenInfoArtifact(true);
 		}
-		BlackboardArtifact.Type artifactType = db.getArtifactType(artifactTypeID);
+		BlackboardArtifact.Type artifactType = db.getBlackboard().getArtifactType(artifactTypeID);
 		switch (artifactType.getCategory()) {
 			case DATA_ARTIFACT:
 				return this.newDataArtifact(artifactType, Collections.emptyList());
@@ -411,7 +411,7 @@ public BlackboardArtifact newArtifact(BlackboardArtifact.ARTIFACT_TYPE type) thr
 
 	@Override
 	public ArrayList<BlackboardArtifact> getArtifacts(String artifactTypeName) throws TskCoreException {
-		return getArtifacts(db.getArtifactType(artifactTypeName).getTypeID());
+		return getArtifacts(db.getBlackboard().getArtifactType(artifactTypeName).getTypeID());
 	}
 
 	@Override
diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java
index 3ff266e1e..2909c6e10 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java
@@ -537,7 +537,7 @@ public String getSha256Hash() {
 	public List<Attribute> getAttributes() throws TskCoreException {
 		synchronized (this) {
 			if (!loadedAttributesCacheFromDb) {
-				ArrayList<Attribute> attributes = getSleuthkitCase().getFileAttributes(this);
+				ArrayList<Attribute> attributes = getSleuthkitCase().getBlackboard().getFileAttributes(this);
 				fileAttributesCache.clear();
 				fileAttributesCache.addAll(attributes);
 				loadedAttributesCacheFromDb = true;
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
index 13d0aee19..8c2b57170 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
@@ -29,13 +29,18 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
+import static org.sleuthkit.datamodel.SleuthkitCase.closeConnection;
+import static org.sleuthkit.datamodel.SleuthkitCase.closeResultSet;
+import static org.sleuthkit.datamodel.SleuthkitCase.closeStatement;
 
 /**
  * A representation of the blackboard, a place where artifacts and their
@@ -45,6 +50,20 @@ public final class Blackboard {
 
 	private static final Logger LOGGER = Logger.getLogger(Blackboard.class.getName());
 
+	/*
+	 * ConcurrentHashMap semantics are fine for these caches to which entries
+	 * are added, but never removed. There is also no need to keep each pair of
+	 * related caches strictly consistent with each other, because cache misses
+	 * will be extremely rare (standard types are loaded when the case is
+	 * opened), and the cost of a cache miss is low.
+	 */
+	private final Map<Integer, BlackboardArtifact.Type> typeIdToArtifactTypeMap = new ConcurrentHashMap<>();
+	private final Map<Integer, BlackboardAttribute.Type> typeIdToAttributeTypeMap = new ConcurrentHashMap<>();
+	private final Map<String, BlackboardArtifact.Type> typeNameToArtifactTypeMap = new ConcurrentHashMap<>();
+	private final Map<String, BlackboardAttribute.Type> typeNameToAttributeTypeMap = new ConcurrentHashMap<>();
+
+	static final int MIN_USER_DEFINED_TYPE_ID = 10000;	
+	
 	private final SleuthkitCase caseDb;
 
 	/**
@@ -134,25 +153,489 @@ public BlackboardArtifact.Type getOrAddArtifactType(String typeName, String disp
 	 * @throws BlackboardException If there is a problem getting or adding the
 	 *                             artifact type.
 	 */
-    @SuppressWarnings("deprecation")	
 	public BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName, BlackboardArtifact.Category category) throws BlackboardException {
 		if (category == null) {
 			throw new BlackboardException("Category provided must be non-null");
 		}
 
+		if (typeNameToArtifactTypeMap.containsKey(typeName)) {
+			return typeNameToArtifactTypeMap.get(typeName);
+		}
+		
+		Statement s = null;
+		ResultSet rs = null;
+		CaseDbTransaction trans = null;
 		try {
-			return caseDb.addBlackboardArtifactType(typeName, displayName, category);
-		} catch (TskDataException typeExistsEx) {
+			trans = caseDb.beginTransaction();
+
+			CaseDbConnection connection = trans.getConnection();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT artifact_type_id FROM blackboard_artifact_types WHERE type_name = '" + typeName + "'"); //NON-NLS
+			if (!rs.next()) {
+				rs.close();
+				rs = connection.executeQuery(s, "SELECT MAX(artifact_type_id) AS highest_id FROM blackboard_artifact_types");
+				int maxID = 0;
+				if (rs.next()) {
+					maxID = rs.getInt("highest_id");
+					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
+						maxID = MIN_USER_DEFINED_TYPE_ID;
+					} else {
+						maxID++;
+					}
+				}
+				connection.executeUpdate(s, "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES ('" + maxID + "', '" + typeName + "', '" + displayName + "', " + category.getID() + " )"); //NON-NLS
+				BlackboardArtifact.Type type = new BlackboardArtifact.Type(maxID, typeName, displayName, category);
+				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
+				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
+				trans.commit();
+				trans = null;
+				return type;
+			} else {
+				trans.commit();
+				trans = null;
+				try {
+					return getArtifactType(typeName);
+				} catch (TskCoreException ex) {
+					throw new BlackboardException("Failed to get or add artifact type: " + typeName, ex);
+				}
+			}
+		} catch (SQLException | TskCoreException ex) {
 			try {
-				return caseDb.getArtifactType(typeName);
-			} catch (TskCoreException ex) {
-				throw new BlackboardException("Failed to get or add artifact type", ex);
+				if (trans != null) {
+					trans.rollback();
+					trans = null;
+				}
+			} catch (TskCoreException ex2) {
+				LOGGER.log(Level.SEVERE, "Error rolling back transaction", ex2);
+			}
+			throw new BlackboardException("Error adding artifact type: " + typeName, ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			if (trans != null) {
+				try {
+					trans.rollback();
+				} catch (TskCoreException ex) {
+					throw new BlackboardException("Error rolling back transaction", ex);
+				}
 			}
-		} catch (TskCoreException ex) {
-			throw new BlackboardException("Failed to get or add artifact type", ex);
 		}
 	}
+	
+	/**
+	 * Get the attribute type associated with an attribute type name.
+	 *
+	 * @param attrTypeName An attribute type name.
+	 *
+	 * @return An attribute type or null if the attribute type does not exist.
+	 *
+	 * @throws TskCoreException If an error occurs accessing the case database.
+	 *
+	 */
+	public BlackboardAttribute.Type getAttributeType(String attrTypeName) throws TskCoreException {
+		if (this.typeNameToAttributeTypeMap.containsKey(attrTypeName)) {
+			return this.typeNameToAttributeTypeMap.get(attrTypeName);
+		}
+		CaseDbConnection connection = null;
+		Statement s = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types WHERE type_name = '" + attrTypeName + "'"); //NON-NLS
+			BlackboardAttribute.Type type = null;
+			if (rs.next()) {
+				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
+						rs.getString("display_name"), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
+				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
+				this.typeNameToAttributeTypeMap.put(attrTypeName, type);
+			}
+			return type;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting attribute type id", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}	
+	
+	/**
+	 * Get the attribute type associated with an attribute type ID.
+	 *
+	 * @param typeID An attribute type ID.
+	 *
+	 * @return An attribute type or null if the attribute type does not exist.
+	 *
+	 * @throws TskCoreException If an error occurs accessing the case database.
+	 *
+	 */
+	BlackboardAttribute.Type getAttributeType(int typeID) throws TskCoreException {
+		if (this.typeIdToAttributeTypeMap.containsKey(typeID)) {
+			return this.typeIdToAttributeTypeMap.get(typeID);
+		}
+		CaseDbConnection connection = null;
+		Statement s = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types WHERE attribute_type_id = " + typeID + ""); //NON-NLS
+			BlackboardAttribute.Type type = null;
+			if (rs.next()) {
+				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
+						rs.getString("display_name"), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
+				this.typeIdToAttributeTypeMap.put(typeID, type);
+				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
+			}
+			return type;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting attribute type id", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}	
+	
+	/**
+	 * Get the artifact type associated with an artifact type name.
+	 *
+	 * @param artTypeName An artifact type name.
+	 *
+	 * @return An artifact type or null if the artifact type does not exist.
+	 *
+	 * @throws TskCoreException If an error occurs accessing the case database.
+	 *
+	 */
+	public BlackboardArtifact.Type getArtifactType(String artTypeName) throws TskCoreException {
+		if (this.typeNameToArtifactTypeMap.containsKey(artTypeName)) {
+			return this.typeNameToArtifactTypeMap.get(artTypeName);
+		}
+		CaseDbConnection connection = null;
+		Statement s = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types WHERE type_name = '" + artTypeName + "'"); //NON-NLS
+			BlackboardArtifact.Type type = null;
+			if (rs.next()) {
+				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
+						rs.getString("type_name"), rs.getString("display_name"),
+						BlackboardArtifact.Category.fromID(rs.getInt("category_type")));
+				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
+				this.typeNameToArtifactTypeMap.put(artTypeName, type);
+			}
+			return type;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting artifact type from the database", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}	
+	
+	/**
+	 * Get the artifact type associated with an artifact type id.
+	 *
+	 * @param artTypeId An artifact type id.
+	 *
+	 * @return The artifact type.
+	 *
+	 * @throws TskCoreException If an error occurs accessing the case database
+	 *                          or no value is found.
+	 *
+	 */
+	public BlackboardArtifact.Type getArtifactType(int artTypeId) throws TskCoreException {
+		if (this.typeIdToArtifactTypeMap.containsKey(artTypeId)) {
+			return typeIdToArtifactTypeMap.get(artTypeId);
+		}
+		CaseDbConnection connection = null;
+		Statement s = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types WHERE artifact_type_id = " + artTypeId + ""); //NON-NLS
+			BlackboardArtifact.Type type = null;
+			if (rs.next()) {
+				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
+						rs.getString("type_name"), rs.getString("display_name"),
+						BlackboardArtifact.Category.fromID(rs.getInt("category_type")));
+				this.typeIdToArtifactTypeMap.put(artTypeId, type);
+				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
+				return type;
+			} else {
+				throw new TskCoreException("No artifact type found matching id: " + artTypeId);
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting artifact type from the database", ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}	
+	
+	/**
+	 * Get the list of attributes for the given artifact.
+	 * 
+	 * @param artifact The artifact to load attributes for.
+	 * 
+	 * @return The list of attributes.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardArtifact artifact) throws TskCoreException {
+		CaseDbConnection connection = null;
+		Statement statement = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			statement = connection.createStatement();
+			rs = connection.executeQuery(statement, "SELECT attrs.artifact_id AS artifact_id, "
+					+ "attrs.source AS source, attrs.context AS context, attrs.attribute_type_id AS attribute_type_id, "
+					+ "attrs.value_type AS value_type, attrs.value_byte AS value_byte, "
+					+ "attrs.value_text AS value_text, attrs.value_int32 AS value_int32, "
+					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
+					+ "types.type_name AS type_name, types.display_name AS display_name "
+					+ "FROM blackboard_attributes AS attrs, blackboard_attribute_types AS types WHERE attrs.artifact_id = " + artifact.getArtifactID()
+					+ " AND attrs.attribute_type_id = types.attribute_type_id");
+			ArrayList<BlackboardAttribute> attributes = new ArrayList<>();
+			while (rs.next()) {
+				int attributeTypeId = rs.getInt("attribute_type_id");
+				String attributeTypeName = rs.getString("type_name");
+				BlackboardAttribute.Type attributeType;
+				if (this.typeIdToAttributeTypeMap.containsKey(attributeTypeId)) {
+					attributeType = this.typeIdToAttributeTypeMap.get(attributeTypeId);
+				} else {
+					attributeType = new BlackboardAttribute.Type(attributeTypeId, attributeTypeName,
+							rs.getString("display_name"),
+							BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getInt("value_type")));
+					this.typeIdToAttributeTypeMap.put(attributeTypeId, attributeType);
+					this.typeNameToAttributeTypeMap.put(attributeTypeName, attributeType);
+				}
+
+				final BlackboardAttribute attr = new BlackboardAttribute(
+						rs.getLong("artifact_id"),
+						attributeType,
+						rs.getString("source"),
+						rs.getString("context"),
+						rs.getInt("value_int32"),
+						rs.getLong("value_int64"),
+						rs.getDouble("value_double"),
+						rs.getString("value_text"),
+						rs.getBytes("value_byte"), caseDb
+				);
+				attr.setParentDataSourceID(artifact.getDataSourceObjectID());
+				attributes.add(attr);
+			}
+			return attributes;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting attributes for artifact, artifact id = " + artifact.getArtifactID(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(statement);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}	
+
+	/**
+	 * Get the attributes associated with the given file.
+	 *
+	 * @param file
+	 *
+	 * @return
+	 *
+	 * @throws TskCoreException
+	 */
+	ArrayList<Attribute> getFileAttributes(final AbstractFile file) throws TskCoreException {
+		CaseDbConnection connection = null;
+		Statement statement = null;
+		ResultSet rs = null;
+		caseDb.acquireSingleUserCaseReadLock();
+		try {
+			connection = caseDb.getConnection();
+			statement = connection.createStatement();
+			rs = connection.executeQuery(statement, "SELECT attrs.id as id,  attrs.obj_id AS obj_id, "
+					+ "attrs.attribute_type_id AS attribute_type_id, "
+					+ "attrs.value_type AS value_type, attrs.value_byte AS value_byte, "
+					+ "attrs.value_text AS value_text, attrs.value_int32 AS value_int32, "
+					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
+					+ "types.type_name AS type_name, types.display_name AS display_name "
+					+ "FROM tsk_file_attributes AS attrs "
+					+ " INNER JOIN blackboard_attribute_types AS types "
+					+ " ON attrs.attribute_type_id = types.attribute_type_id "
+					+ " WHERE attrs.obj_id = " + file.getId());
+
+			ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+			while (rs.next()) {
+				int attributeTypeId = rs.getInt("attribute_type_id");
+				String attributeTypeName = rs.getString("type_name");
+				BlackboardAttribute.Type attributeType;
+				if (this.typeIdToAttributeTypeMap.containsKey(attributeTypeId)) {
+					attributeType = this.typeIdToAttributeTypeMap.get(attributeTypeId);
+				} else {
+					attributeType = new BlackboardAttribute.Type(attributeTypeId, attributeTypeName,
+							rs.getString("display_name"),
+							BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getInt("value_type")));
+					this.typeIdToAttributeTypeMap.put(attributeTypeId, attributeType);
+					this.typeNameToAttributeTypeMap.put(attributeTypeName, attributeType);
+				}
+
+				final Attribute attr = new Attribute(
+						rs.getLong("id"),
+						rs.getLong("obj_id"),
+						attributeType,
+						rs.getInt("value_int32"),
+						rs.getLong("value_int64"),
+						rs.getDouble("value_double"),
+						rs.getString("value_text"),
+						rs.getBytes("value_byte"), caseDb
+				);
+				attributes.add(attr);
+			}
+			return attributes;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting attributes for file, file id = " + file.getId(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(statement);
+			closeConnection(connection);
+			caseDb.releaseSingleUserCaseReadLock();
+		}
+	}
+	
+	/**
+	 * Adds the standard artifact types to the blackboard_artifact_types table
+	 * and the artifact type caches.
+	 *
+	 * @param connection A connection to the case database.
+	 *
+	 * @throws SQLException Thrown if there is an error adding a type to the
+	 *                      table.
+	 */
+	void initBlackboardArtifactTypes(CaseDbConnection connection) throws SQLException {
+		caseDb.acquireSingleUserCaseWriteLock();
+		try (Statement statement = connection.createStatement()) {
+			/*
+			 * Determine which types, if any, have already been added to the
+			 * case database, and load them into the type caches. For a case
+			 * that is being reopened, this should reduce the number of separate
+			 * INSERT staements that will be executed below.
+			 */
+			ResultSet resultSet = connection.executeQuery(statement, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types"); //NON-NLS
+			while (resultSet.next()) {
+				BlackboardArtifact.Type type = new BlackboardArtifact.Type(resultSet.getInt("artifact_type_id"),
+						resultSet.getString("type_name"), resultSet.getString("display_name"),
+						BlackboardArtifact.Category.fromID(resultSet.getInt("category_type")));
+				typeIdToArtifactTypeMap.put(type.getTypeID(), type);
+				typeNameToArtifactTypeMap.put(type.getTypeName(), type);
+			}
+
+			/*
+			 * INSERT any missing standard types. A conflict clause is used to
+			 * avoid a potential race condition. It also eliminates the need to
+			 * add schema update code when new types are added.
+			 *
+			 * The use here of the soon to be deprecated
+			 * BlackboardArtifact.ARTIFACT_TYPE enum instead of the
+			 * BlackboardArtifact.Type.STANDARD_TYPES collection currently
+			 * ensures that the deprecated types in the former, and not in the
+			 * latter, are added to the case database.
+			 */
+			for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) {
+				if (typeIdToArtifactTypeMap.containsKey(type.getTypeID())) {
+					continue;
+				}
+				if (caseDb.getDatabaseType() == TskData.DbType.POSTGRESQL) {
+					statement.execute("INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES (" + type.getTypeID() + " , '" + type.getLabel() + "', '" + type.getDisplayName() + "' , " + type.getCategory().getID() + ") ON CONFLICT DO NOTHING"); //NON-NLS
+				} else {
+					statement.execute("INSERT OR IGNORE INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES (" + type.getTypeID() + " , '" + type.getLabel() + "', '" + type.getDisplayName() + "' , " + type.getCategory().getID() + ")"); //NON-NLS
+				}
+				typeIdToArtifactTypeMap.put(type.getTypeID(), new BlackboardArtifact.Type(type));
+				typeNameToArtifactTypeMap.put(type.getLabel(), new BlackboardArtifact.Type(type));
+			}
+			if (caseDb.getDatabaseType() == TskData.DbType.POSTGRESQL) {
+				int newPrimaryKeyIndex = Collections.max(Arrays.asList(BlackboardArtifact.ARTIFACT_TYPE.values())).getTypeID() + 1;
+				statement.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
+			}
+		} finally {
+			caseDb.releaseSingleUserCaseWriteLock();
+		}
+	}	
+	
+	/**
+	 * Adds the standard attribute types to the blackboard_attribute_types table
+	 * and the attribute type caches.
+	 *
+	 * @param connection A connection to the case database.
+	 *
+	 * @throws SQLException Thrown if there is an error adding a type to the
+	 *                      table.
+	 */
+	void initBlackboardAttributeTypes(CaseDbConnection connection) throws SQLException {
+		caseDb.acquireSingleUserCaseWriteLock();
+		try (Statement statement = connection.createStatement()) {
+			/*
+			 * Determine which types, if any, have already been added to the
+			 * case database, and load them into the type caches. For a case
+			 * that is being reopened, this should reduce the number of separate
+			 * INSERT staements that will be executed below.
+			 */
+			ResultSet resultSet = connection.executeQuery(statement, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types"); //NON-NLS
+			while (resultSet.next()) {
+				BlackboardAttribute.Type type = new BlackboardAttribute.Type(resultSet.getInt("attribute_type_id"),
+						resultSet.getString("type_name"), resultSet.getString("display_name"),
+						BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(resultSet.getLong("value_type")));
+				typeIdToAttributeTypeMap.put(type.getTypeID(), type);
+				typeNameToAttributeTypeMap.put(type.getTypeName(), type);
+			}
 
+			/*
+			 * INSERT any missing standard types. A conflict clause is used to
+			 * avoid a potential race condition. It also eliminates the need to
+			 * add schema update code when new types are added.
+			 *
+			 * The use here of the soon to be deprecated
+			 * BlackboardAttribute.ATTRIBUTE_TYPE enum instead of the
+			 * BlackboardAttribute.Type.STANDARD_TYPES collection currently
+			 * ensures that the deprecated types in the former, and not in the
+			 * latter, are added to the case database.
+			 */
+			for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
+				if (typeIdToAttributeTypeMap.containsKey(type.getTypeID())) {
+					continue;
+				}
+				if (caseDb.getDatabaseType() == TskData.DbType.POSTGRESQL) {
+					statement.execute("INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES (" + type.getTypeID() + ", '" + type.getLabel() + "', '" + type.getDisplayName() + "', '" + type.getValueType().getType() + "') ON CONFLICT DO NOTHING"); //NON-NLS
+				} else {
+					statement.execute("INSERT OR IGNORE INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES (" + type.getTypeID() + ", '" + type.getLabel() + "', '" + type.getDisplayName() + "', '" + type.getValueType().getType() + "')"); //NON-NLS
+				}
+				typeIdToAttributeTypeMap.put(type.getTypeID(), new BlackboardAttribute.Type(type));
+				typeNameToAttributeTypeMap.put(type.getLabel(), new BlackboardAttribute.Type(type));
+			}
+			if (caseDb.getDatabaseType() == TskData.DbType.POSTGRESQL) {
+				int newPrimaryKeyIndex = Collections.max(Arrays.asList(BlackboardAttribute.ATTRIBUTE_TYPE.values())).getTypeID() + 1;
+				statement.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
+			}
+		} finally {
+			caseDb.releaseSingleUserCaseWriteLock();
+		}
+	}	
+	
 	/**
 	 * Adds new analysis result artifact.
 	 *
@@ -509,7 +992,7 @@ List<AnalysisResult> getAnalysisResults(long sourceObjId, CaseDbConnection conne
 	 */
 	public List<AnalysisResult> getAnalysisResults(long sourceObjId, int artifactTypeId) throws TskCoreException {
 		// Get the artifact type to check that it in the analysis result category.
-		BlackboardArtifact.Type artifactType = caseDb.getArtifactType(artifactTypeId);
+		BlackboardArtifact.Type artifactType = getArtifactType(artifactTypeId);
 		if (artifactType.getCategory() != BlackboardArtifact.Category.ANALYSIS_RESULT) {
 			throw new TskCoreException(String.format("Artifact type id %d is not in analysis result catgeory.", artifactTypeId));
 		}
@@ -673,7 +1156,7 @@ public List<DataArtifact> getDataArtifacts(long dataSourceObjId, Integer artifac
 	public List<DataArtifact> getDataArtifacts(int artifactTypeID, long dataSourceObjId) throws TskCoreException {
 
 		// Get the artifact type to check that it in the data artifact category.
-		BlackboardArtifact.Type artifactType = caseDb.getArtifactType(artifactTypeID);
+		BlackboardArtifact.Type artifactType = getArtifactType(artifactTypeID);
 		if (artifactType.getCategory() != BlackboardArtifact.Category.DATA_ARTIFACT) {
 			throw new TskCoreException(String.format("Artifact type id %d is not in data artifact catgeory.", artifactTypeID));
 		}
@@ -701,7 +1184,7 @@ public List<DataArtifact> getDataArtifacts(int artifactTypeID, long dataSourceOb
 	 */
 	public List<DataArtifact> getDataArtifacts(int artifactTypeID) throws TskCoreException {
 		// Get the artifact type to check that it in the data artifact category.
-		BlackboardArtifact.Type artifactType = caseDb.getArtifactType(artifactTypeID);
+		BlackboardArtifact.Type artifactType = getArtifactType(artifactTypeID);
 		if (artifactType.getCategory() != BlackboardArtifact.Category.DATA_ARTIFACT) {
 			throw new TskCoreException(String.format("Artifact type id %d is not in data artifact catgeory.", artifactTypeID));
 		}
@@ -827,21 +1310,6 @@ private List<DataArtifact> resultSetToDataArtifacts(ResultSet resultSet, CaseDbC
 		return dataArtifacts;
 	}
 
-	/**
-	 * Get the artifact type associated with an artifact type id.
-	 *
-	 * @param artTypeId An artifact type id.
-	 *
-	 * @return The artifact type.
-	 *
-	 * @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.
@@ -855,20 +1323,70 @@ public BlackboardArtifact.Type getArtifactType(int artTypeId) throws TskCoreExce
 	 * @throws BlackboardException If there is a problem getting or adding the
 	 *                             attribute type.
 	 */
-    @SuppressWarnings("deprecation")	
 	public BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException {
 
+		if (typeNameToAttributeTypeMap.containsKey(typeName)) {
+			return typeNameToAttributeTypeMap.get(typeName);
+		}
+		
+		CaseDbTransaction trans = null;
+		Statement s = null;
+		ResultSet rs = null;
 		try {
-			return caseDb.addArtifactAttributeType(typeName, valueType, displayName);
-		} catch (TskDataException typeExistsEx) {
+			trans = caseDb.beginTransaction();
+			CaseDbConnection connection = trans.getConnection();
+			connection.beginTransaction();
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, "SELECT attribute_type_id FROM blackboard_attribute_types WHERE type_name = '" + typeName + "'"); //NON-NLS
+			if (!rs.next()) {
+				rs.close();
+				rs = connection.executeQuery(s, "SELECT MAX(attribute_type_id) AS highest_id FROM blackboard_attribute_types");
+				int maxID = 0;
+				if (rs.next()) {
+					maxID = rs.getInt("highest_id");
+					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
+						maxID = MIN_USER_DEFINED_TYPE_ID;
+					} else {
+						maxID++;
+					}
+				}
+				connection.executeUpdate(s, "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES ('" + maxID + "', '" + typeName + "', '" + displayName + "', '" + valueType.getType() + "')"); //NON-NLS
+				BlackboardAttribute.Type type = new BlackboardAttribute.Type(maxID, typeName, displayName, valueType);
+				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
+				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
+				trans.commit();
+				trans = null;
+				return type;
+			} else {
+				trans.commit();
+				trans = null;
+				try {
+					return getAttributeType(typeName);
+				} catch (TskCoreException ex) {
+					throw new BlackboardException("Failed to get or add attribute type: " + typeName, ex);
+				}
+			}
+		} catch (SQLException | TskCoreException ex) {
 			try {
-				return caseDb.getAttributeType(typeName);
-			} catch (TskCoreException ex) {
-				throw new BlackboardException("Failed to get or add attribute type", ex);
+				if (trans != null) {
+					trans.rollback();
+					trans = null;
+				}
+			} catch (TskCoreException ex2) {
+				LOGGER.log(Level.SEVERE, "Error rolling back transaction", ex2);
 			}
-		} catch (TskCoreException ex) {
-			throw new BlackboardException("Failed to get or add attribute type", ex);
-		}
+			throw new BlackboardException("Error adding attribute type: " + typeName, ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			if (trans != null) {
+				try {
+					trans.rollback();
+				} catch (TskCoreException ex) {
+					throw new BlackboardException("Error rolling back transaction", ex);
+				}
+			}
+		}		
 	}
 
 	/**
@@ -943,7 +1461,7 @@ public long getArtifactsCount(int artifactTypeID, long dataSourceObjId) throws T
 	 */
 	public List<BlackboardArtifact> getArtifacts(int artifactTypeID, long dataSourceObjId) throws TskCoreException {
 		String whereClause = String.format("artifacts.data_source_obj_id = %d", dataSourceObjId);
-		return getArtifactsWhere(caseDb.getArtifactType(artifactTypeID), whereClause);
+		return getArtifactsWhere(getArtifactType(artifactTypeID), whereClause);
 	}
 
 	/**
@@ -1381,7 +1899,7 @@ private ArtifactsPostedEvent(Collection<BlackboardArtifact> artifacts, String mo
 			Set<BlackboardArtifact.Type> types = new HashSet<>();
 			for (Integer typeID : typeIDS) {
 				try {
-					types.add(caseDb.getArtifactType(typeID));
+					types.add(getArtifactType(typeID));
 				} catch (TskCoreException tskCoreException) {
 					throw new BlackboardException("Error getting artifact type by id.", tskCoreException);
 				}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
index 9ba96d00e..2486f229a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
@@ -194,13 +194,15 @@ public int getArtifactTypeID() {
 	 * Gets the artifact type for this artifact.
 	 *
 	 * @return The artifact type.
+	 * 
+	 * @throws TskCoreException
 	 */
 	public BlackboardArtifact.Type getType() throws TskCoreException {
 		BlackboardArtifact.Type standardTypesValue = BlackboardArtifact.Type.STANDARD_TYPES.get(getArtifactTypeID());
 		if (standardTypesValue != null) {
 			return standardTypesValue;
 		} else {
-			return getSleuthkitCase().getArtifactType(getArtifactTypeID());
+			return getSleuthkitCase().getBlackboard().getArtifactType(getArtifactTypeID());
 		}
 	}
 
@@ -416,12 +418,12 @@ public void addAttribute(BlackboardAttribute attribute) throws TskCoreException
 	public List<BlackboardAttribute> getAttributes() throws TskCoreException {
 		ArrayList<BlackboardAttribute> attributes;
 		if (false == loadedCacheFromDb) {
-			attributes = getSleuthkitCase().getBlackboardAttributes(this);
+			attributes = getSleuthkitCase().getBlackboard().getBlackboardAttributes(this);
 			attrsCache.clear();
 			attrsCache.addAll(attributes);
 			loadedCacheFromDb = true;
 		} else {
-			attributes = new ArrayList<BlackboardAttribute>(attrsCache);
+			attributes = new ArrayList<>(attrsCache);
 		}
 		return attributes;
 	}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
index d6f6b3e44..79686226d 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
@@ -552,7 +552,7 @@ private BlackboardArtifact getAccountFileInstanceArtifact(Account.Type accountTy
 			Statement s = connection.createStatement();
 			ResultSet rs = connection.executeQuery(s, queryStr);) { //NON-NLS
 			if (rs.next()) {
-				BlackboardArtifact.Type bbartType = db.getArtifactType(rs.getInt("artifact_type_id"));
+				BlackboardArtifact.Type bbartType = db.getBlackboard().getArtifactType(rs.getInt("artifact_type_id"));
 
 				accountArtifact = new DataArtifact(db, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"),
 						rs.getObject("data_source_obj_id") != null ? rs.getLong("data_source_obj_id") : null,
@@ -1390,7 +1390,7 @@ private String getMostRecentFilterLimitSQL(CommunicationsFilter filter) {
 	private List<BlackboardArtifact> getDataArtifactsFromResult(ResultSet resultSet) throws SQLException, TskCoreException {
 		List<BlackboardArtifact> artifacts = new ArrayList<>();
 		while (resultSet.next()) {
-			BlackboardArtifact.Type bbartType = db.getArtifactType(resultSet.getInt("artifact_type_id"));
+			BlackboardArtifact.Type bbartType = db.getBlackboard().getArtifactType(resultSet.getInt("artifact_type_id"));
 			artifacts.add(new DataArtifact(db, resultSet.getLong("artifact_id"),
 					resultSet.getLong("obj_id"), resultSet.getLong("artifact_obj_id"),
 					resultSet.getObject("data_source_obj_id") != null ? resultSet.getLong("data_source_obj_id") : null,
diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java
index 73be3d184..35a581216 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java
@@ -1187,7 +1187,7 @@ List<OsAccountAttribute> getOsAccountAttributes(OsAccount account) throws TskCor
 				if (!rs.wasNull()) {
 					sourceContent = this.db.getContentById(sourceObjId);
 				}
-				BlackboardAttribute.Type attributeType = db.getAttributeType(rs.getInt("attribute_type_id"));
+				BlackboardAttribute.Type attributeType = db.getBlackboard().getAttributeType(rs.getInt("attribute_type_id"));
 				OsAccountAttribute attribute = account.new OsAccountAttribute(attributeType, rs.getInt("value_int32"), rs.getLong("value_int64"),
 						rs.getDouble("value_double"), rs.getString("value_text"), rs.getBytes("value_byte"),
 						db, account, host, sourceContent);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Report.java b/bindings/java/src/org/sleuthkit/datamodel/Report.java
index 51d6c930e..6a080f758 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Report.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Report.java
@@ -314,7 +314,7 @@ public BlackboardArtifact newArtifact(BlackboardArtifact.ARTIFACT_TYPE type) thr
 
 	@Override
 	public ArrayList<BlackboardArtifact> getArtifacts(String artifactTypeName) throws TskCoreException {
-		return getArtifacts(db.getArtifactType(artifactTypeName).getTypeID());
+		return getArtifacts(db.getBlackboard().getArtifactType(artifactTypeName).getTypeID());
 	}
 
 	@Override
@@ -380,7 +380,7 @@ public Set<String> getHashSetNames() throws TskCoreException {
 
 	@Override
 	public long getArtifactsCount(String artifactTypeName) throws TskCoreException {
-		return getArtifactsCount(db.getArtifactType(artifactTypeName).getTypeID());
+		return getArtifactsCount(db.getBlackboard().getArtifactType(artifactTypeName).getTypeID());
 	}
 
 	@Override
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 13ee5d484..ccd26de7b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -119,7 +119,6 @@ public class SleuthkitCase {
 	private static final String SQL_ERROR_RESOURCE_GROUP = "53";
 	private static final String SQL_ERROR_LIMIT_GROUP = "54";
 	private static final String SQL_ERROR_INTERNAL_GROUP = "xx";
-	private static final int MIN_USER_DEFINED_TYPE_ID = 10000;
 
 	private static final Set<String> CORE_TABLE_NAMES = ImmutableSet.of(
 			"tsk_events",
@@ -198,18 +197,6 @@ public class SleuthkitCase {
 	private final String caseHandleIdentifier; // Used to identify this case in the JNI cache.
 	private String dbBackupPath;
 
-	/*
-	 * ConcurrentHashMap semantics are fine for these caches to which entries
-	 * are added, but never removed. There is also no need to keep each pair of
-	 * related caches strictly consistent with each other, because cache misses
-	 * will be extremely rare (standard types are loaded when the case is
-	 * opened), and the cost of a cache miss is low.
-	 */
-	private final Map<Integer, BlackboardArtifact.Type> typeIdToArtifactTypeMap = new ConcurrentHashMap<>();
-	private final Map<Integer, BlackboardAttribute.Type> typeIdToAttributeTypeMap = new ConcurrentHashMap<>();
-	private final Map<String, BlackboardArtifact.Type> typeNameToArtifactTypeMap = new ConcurrentHashMap<>();
-	private final Map<String, BlackboardAttribute.Type> typeNameToAttributeTypeMap = new ConcurrentHashMap<>();
-
 	private CaseDbSchemaVersionNumber caseDBSchemaCreationVersion;
 
 	// Objects for caching the result of isRootDirectory(). Lock is for visibility only.
@@ -392,10 +379,11 @@ private SleuthkitCase(String host, int port, String dbName, String userName, Str
 	}
 
 	private void init() throws Exception {
+		blackboard = new Blackboard(this);
 		updateDatabaseSchema(null);
 		try (CaseDbConnection connection = connections.getConnection()) {
-			initBlackboardArtifactTypes(connection);
-			initBlackboardAttributeTypes(connection);
+			blackboard.initBlackboardArtifactTypes(connection);
+			blackboard.initBlackboardAttributeTypes(connection);
 			initNextArtifactId(connection);
 			initIngestModuleTypes(connection);
 			initIngestStatusTypes(connection);
@@ -406,7 +394,6 @@ private void init() throws Exception {
 			initDBSchemaCreationVersion(connection);
 		}
 
-		blackboard = new Blackboard(this);
 		fileManager = new FileManager(this);
 		communicationsMgr = new CommunicationsManager(this);
 		timelineMgr = new TimelineManager(this);
@@ -605,124 +592,6 @@ public HostAddressManager getHostAddressManager() throws TskCoreException {
 		return hostAddressManager;
 	}
 
-	/**
-	 * Adds the standard artifact types to the blackboard_artifact_types table
-	 * and the artifact type caches.
-	 *
-	 * @param connection A connection to the case database.
-	 *
-	 * @throws SQLException Thrown if there is an error adding a type to the
-	 *                      table.
-	 */
-	private void initBlackboardArtifactTypes(CaseDbConnection connection) throws SQLException {
-		acquireSingleUserCaseWriteLock();
-		try (Statement statement = connection.createStatement()) {
-			/*
-			 * Determine which types, if any, have already been added to the
-			 * case database, and load them into the type caches. For a case
-			 * that is being reopened, this should reduce the number of separate
-			 * INSERT staements that will be executed below.
-			 */
-			ResultSet resultSet = connection.executeQuery(statement, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types"); //NON-NLS
-			while (resultSet.next()) {
-				BlackboardArtifact.Type type = new BlackboardArtifact.Type(resultSet.getInt("artifact_type_id"),
-						resultSet.getString("type_name"), resultSet.getString("display_name"),
-						BlackboardArtifact.Category.fromID(resultSet.getInt("category_type")));
-				typeIdToArtifactTypeMap.put(type.getTypeID(), type);
-				typeNameToArtifactTypeMap.put(type.getTypeName(), type);
-			}
-
-			/*
-			 * INSERT any missing standard types. A conflict clause is used to
-			 * avoid a potential race condition. It also eliminates the need to
-			 * add schema update code when new types are added.
-			 *
-			 * The use here of the soon to be deprecated
-			 * BlackboardArtifact.ARTIFACT_TYPE enum instead of the
-			 * BlackboardArtifact.Type.STANDARD_TYPES collection currently
-			 * ensures that the deprecated types in the former, and not in the
-			 * latter, are added to the case database.
-			 */
-			for (ARTIFACT_TYPE type : ARTIFACT_TYPE.values()) {
-				if (typeIdToArtifactTypeMap.containsKey(type.getTypeID())) {
-					continue;
-				}
-				if (dbType == DbType.POSTGRESQL) {
-					statement.execute("INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES (" + type.getTypeID() + " , '" + type.getLabel() + "', '" + type.getDisplayName() + "' , " + type.getCategory().getID() + ") ON CONFLICT DO NOTHING"); //NON-NLS
-				} else {
-					statement.execute("INSERT OR IGNORE INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES (" + type.getTypeID() + " , '" + type.getLabel() + "', '" + type.getDisplayName() + "' , " + type.getCategory().getID() + ")"); //NON-NLS
-				}
-				typeIdToArtifactTypeMap.put(type.getTypeID(), new BlackboardArtifact.Type(type));
-				typeNameToArtifactTypeMap.put(type.getLabel(), new BlackboardArtifact.Type(type));
-			}
-			if (dbType == DbType.POSTGRESQL) {
-				int newPrimaryKeyIndex = Collections.max(Arrays.asList(ARTIFACT_TYPE.values())).getTypeID() + 1;
-				statement.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
-			}
-		} finally {
-			releaseSingleUserCaseWriteLock();
-		}
-	}
-
-	/**
-	 * Adds the standard attribute types to the blackboard_attribute_types table
-	 * and the attribute type caches.
-	 *
-	 * @param connection A connection to the case database.
-	 *
-	 * @throws SQLException Thrown if there is an error adding a type to the
-	 *                      table.
-	 */
-	private void initBlackboardAttributeTypes(CaseDbConnection connection) throws SQLException {
-		acquireSingleUserCaseWriteLock();
-		try (Statement statement = connection.createStatement()) {
-			/*
-			 * Determine which types, if any, have already been added to the
-			 * case database, and load them into the type caches. For a case
-			 * that is being reopened, this should reduce the number of separate
-			 * INSERT staements that will be executed below.
-			 */
-			ResultSet resultSet = connection.executeQuery(statement, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types"); //NON-NLS
-			while (resultSet.next()) {
-				BlackboardAttribute.Type type = new BlackboardAttribute.Type(resultSet.getInt("attribute_type_id"),
-						resultSet.getString("type_name"), resultSet.getString("display_name"),
-						TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(resultSet.getLong("value_type")));
-				typeIdToAttributeTypeMap.put(type.getTypeID(), type);
-				typeNameToAttributeTypeMap.put(type.getTypeName(), type);
-			}
-
-			/*
-			 * INSERT any missing standard types. A conflict clause is used to
-			 * avoid a potential race condition. It also eliminates the need to
-			 * add schema update code when new types are added.
-			 *
-			 * The use here of the soon to be deprecated
-			 * BlackboardAttribute.ATTRIBUTE_TYPE enum instead of the
-			 * BlackboardAttribute.Type.STANDARD_TYPES collection currently
-			 * ensures that the deprecated types in the former, and not in the
-			 * latter, are added to the case database.
-			 */
-			for (ATTRIBUTE_TYPE type : ATTRIBUTE_TYPE.values()) {
-				if (typeIdToAttributeTypeMap.containsKey(type.getTypeID())) {
-					continue;
-				}
-				if (dbType == DbType.POSTGRESQL) {
-					statement.execute("INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES (" + type.getTypeID() + ", '" + type.getLabel() + "', '" + type.getDisplayName() + "', '" + type.getValueType().getType() + "') ON CONFLICT DO NOTHING"); //NON-NLS
-				} else {
-					statement.execute("INSERT OR IGNORE INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES (" + type.getTypeID() + ", '" + type.getLabel() + "', '" + type.getDisplayName() + "', '" + type.getValueType().getType() + "')"); //NON-NLS
-				}
-				typeIdToAttributeTypeMap.put(type.getTypeID(), new BlackboardAttribute.Type(type));
-				typeNameToAttributeTypeMap.put(type.getLabel(), new BlackboardAttribute.Type(type));
-			}
-			if (dbType == DbType.POSTGRESQL) {
-				int newPrimaryKeyIndex = Collections.max(Arrays.asList(ATTRIBUTE_TYPE.values())).getTypeID() + 1;
-				statement.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
-			}
-		} finally {
-			releaseSingleUserCaseWriteLock();
-		}
-	}
-
 	/**
 	 * Initializes the next artifact id. If there are entries in the
 	 * blackboard_artifacts table we will use max(artifact_id) + 1 otherwise we
@@ -1402,7 +1271,7 @@ private CaseDbSchemaVersionNumber updateFromSchema3toSchema4(CaseDbSchemaVersion
 			while (resultSet.next()) {
 				int attributeTypeId = resultSet.getInt("attribute_type_id");
 				String attributeLabel = resultSet.getString("type_name");
-				if (attributeTypeId < MIN_USER_DEFINED_TYPE_ID) {
+				if (attributeTypeId < Blackboard.MIN_USER_DEFINED_TYPE_ID) {
 					updateStatement.executeUpdate(
 							"UPDATE blackboard_attribute_types " //NON-NLS
 							+ "SET value_type = " + ATTRIBUTE_TYPE.fromLabel(attributeLabel).getValueType().getType() + " " //NON-NLS
@@ -3601,7 +3470,7 @@ public DataSource getDataSource(long objectId) throws TskDataException, TskCoreE
 	@Deprecated
 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(int artifactTypeID) throws TskCoreException {
 		ArrayList<BlackboardArtifact> artifacts = new ArrayList<>();
-		artifacts.addAll(blackboard.getArtifactsByType(getArtifactType(artifactTypeID)));
+		artifacts.addAll(blackboard.getArtifactsByType(blackboard.getArtifactType(artifactTypeID)));
 		return artifacts;
 	}
 
@@ -3749,7 +3618,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -3813,7 +3682,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -3869,7 +3738,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -3926,7 +3795,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -3983,7 +3852,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -4041,7 +3910,7 @@ public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRI
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -4320,7 +4189,7 @@ public ArrayList<BlackboardArtifact> getBlackboardArtifacts(String artifactTypeN
 	 */
 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(int artifactTypeID, long obj_id) throws TskCoreException {
 		ArrayList<BlackboardArtifact> artifacts = new ArrayList<>();
-		artifacts.addAll(blackboard.getArtifactsBySourceId(getArtifactType(artifactTypeID), obj_id));
+		artifacts.addAll(blackboard.getArtifactsBySourceId(blackboard.getArtifactType(artifactTypeID), obj_id));
 		return artifacts;
 	}
 
@@ -4422,7 +4291,7 @@ public ArrayList<BlackboardArtifact> getBlackboardArtifacts(String artifactTypeN
 	 */
 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(ARTIFACT_TYPE artifactType) throws TskCoreException {
 		ArrayList<BlackboardArtifact> artifacts = new ArrayList<>();
-		artifacts.addAll(blackboard.getArtifactsByType(getArtifactType(artifactType.getTypeID())));
+		artifacts.addAll(blackboard.getArtifactsByType(blackboard.getArtifactType(artifactType.getTypeID())));
 		return artifacts;
 	}
 
@@ -4805,55 +4674,15 @@ String addSourceToArtifactAttribute(BlackboardAttribute attr, String source) thr
 	 *
 	 * @throws TskCoreException exception thrown if a critical error occurs
 	 *                          within tsk core
-	 * @throws TskDataException exception thrown if attribute type was already
-	 *                          in the system
 	 *
 	 * @deprecated Use Blackboard.getOrAddAttributeType() instead.
 	 */
 	@Deprecated
 	public BlackboardAttribute.Type addArtifactAttributeType(String attrTypeString, TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws TskCoreException, TskDataException {
-		if (typeNameToAttributeTypeMap.containsKey(attrTypeString)) {
-			return typeNameToAttributeTypeMap.get(attrTypeString);
-		}
-		CaseDbConnection connection = null;
-		acquireSingleUserCaseWriteLock();
-		Statement s = null;
-		ResultSet rs = null;
 		try {
-			connection = connections.getConnection();
-			connection.beginTransaction();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT attribute_type_id FROM blackboard_attribute_types WHERE type_name = '" + attrTypeString + "'"); //NON-NLS
-			if (!rs.next()) {
-				rs.close();
-				rs = connection.executeQuery(s, "SELECT MAX(attribute_type_id) AS highest_id FROM blackboard_attribute_types");
-				int maxID = 0;
-				if (rs.next()) {
-					maxID = rs.getInt("highest_id");
-					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
-						maxID = MIN_USER_DEFINED_TYPE_ID;
-					} else {
-						maxID++;
-					}
-				}
-				connection.executeUpdate(s, "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name, value_type) VALUES ('" + maxID + "', '" + attrTypeString + "', '" + displayName + "', '" + valueType.getType() + "')"); //NON-NLS
-				BlackboardAttribute.Type type = new BlackboardAttribute.Type(maxID, attrTypeString, displayName, valueType);
-				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
-				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
-				connection.commitTransaction();
-				return type;
-			} else {
-				throw new TskDataException("The attribute type that was added was already within the system.");
-			}
-
-		} catch (SQLException ex) {
-			rollbackTransaction(connection);
-			throw new TskCoreException("Error adding attribute type", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseWriteLock();
+			return blackboard.getOrAddAttributeType(attrTypeString, valueType, displayName);
+		} catch (BlackboardException ex) {
+			throw new TskCoreException("Error adding artifact type: " + attrTypeString, ex);
 		}
 	}
 
@@ -4866,75 +4695,11 @@ public BlackboardAttribute.Type addArtifactAttributeType(String attrTypeString,
 	 *
 	 * @throws TskCoreException If an error occurs accessing the case database.
 	 *
+	 * @deprecated Use Blackboard.getAttributeType instead
 	 */
+	@Deprecated
 	public BlackboardAttribute.Type getAttributeType(String attrTypeName) throws TskCoreException {
-		if (this.typeNameToAttributeTypeMap.containsKey(attrTypeName)) {
-			return this.typeNameToAttributeTypeMap.get(attrTypeName);
-		}
-		CaseDbConnection connection = null;
-		Statement s = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types WHERE type_name = '" + attrTypeName + "'"); //NON-NLS
-			BlackboardAttribute.Type type = null;
-			if (rs.next()) {
-				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
-						rs.getString("display_name"), TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
-				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
-				this.typeNameToAttributeTypeMap.put(attrTypeName, type);
-			}
-			return type;
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting attribute type id", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
-		}
-	}
-
-	/**
-	 * Get the attribute type associated with an attribute type ID.
-	 *
-	 * @param typeID An attribute type ID.
-	 *
-	 * @return An attribute type or null if the attribute type does not exist.
-	 *
-	 * @throws TskCoreException If an error occurs accessing the case database.
-	 *
-	 */
-	BlackboardAttribute.Type getAttributeType(int typeID) throws TskCoreException {
-		if (this.typeIdToAttributeTypeMap.containsKey(typeID)) {
-			return this.typeIdToAttributeTypeMap.get(typeID);
-		}
-		CaseDbConnection connection = null;
-		Statement s = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types WHERE attribute_type_id = " + typeID + ""); //NON-NLS
-			BlackboardAttribute.Type type = null;
-			if (rs.next()) {
-				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
-						rs.getString("display_name"), TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
-				this.typeIdToAttributeTypeMap.put(typeID, type);
-				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
-			}
-			return type;
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting attribute type id", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
-		}
+		return blackboard.getAttributeType(attrTypeName);
 	}
 
 	/**
@@ -4946,80 +4711,11 @@ BlackboardAttribute.Type getAttributeType(int typeID) throws TskCoreException {
 	 *
 	 * @throws TskCoreException If an error occurs accessing the case database.
 	 *
+	 * @deprecated Use Blackboard.getArtifactType instead
 	 */
+	@Deprecated
 	public BlackboardArtifact.Type getArtifactType(String artTypeName) throws TskCoreException {
-		if (this.typeNameToArtifactTypeMap.containsKey(artTypeName)) {
-			return this.typeNameToArtifactTypeMap.get(artTypeName);
-		}
-		CaseDbConnection connection = null;
-		Statement s = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types WHERE type_name = '" + artTypeName + "'"); //NON-NLS
-			BlackboardArtifact.Type type = null;
-			if (rs.next()) {
-				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
-						rs.getString("type_name"), rs.getString("display_name"),
-						BlackboardArtifact.Category.fromID(rs.getInt("category_type")));
-				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
-				this.typeNameToArtifactTypeMap.put(artTypeName, type);
-			}
-			return type;
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting artifact type from the database", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
-		}
-	}
-
-	/**
-	 * Get the artifact type associated with an artifact type id.
-	 *
-	 * @param artTypeId An artifact type id.
-	 *
-	 * @return The artifact type.
-	 *
-	 * @throws TskCoreException If an error occurs accessing the case database
-	 *                          or no value is found.
-	 *
-	 */
-	BlackboardArtifact.Type getArtifactType(int artTypeId) throws TskCoreException {
-		if (this.typeIdToArtifactTypeMap.containsKey(artTypeId)) {
-			return typeIdToArtifactTypeMap.get(artTypeId);
-		}
-		CaseDbConnection connection = null;
-		Statement s = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name, category_type FROM blackboard_artifact_types WHERE artifact_type_id = " + artTypeId + ""); //NON-NLS
-			BlackboardArtifact.Type type = null;
-			if (rs.next()) {
-				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
-						rs.getString("type_name"), rs.getString("display_name"),
-						BlackboardArtifact.Category.fromID(rs.getInt("category_type")));
-				this.typeIdToArtifactTypeMap.put(artTypeId, type);
-				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
-				return type;
-			} else {
-				throw new TskCoreException("No artifact type found matching id: " + artTypeId);
-			}
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting artifact type from the database", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
-		}
+		return blackboard.getArtifactType(artTypeName);
 	}
 
 	/**
@@ -5055,178 +4751,35 @@ public BlackboardArtifact.Type addBlackboardArtifactType(String artifactTypeName
 	 * @return Type of the artifact added.
 	 *
 	 * @throws TskCoreException exception thrown if a critical error occurs
-	 * @throws TskDataException exception thrown if given data is already in db
-	 *                          within tsk core
+	 * 
 	 * @deprecated Use Blackboard.getOrAddArtifactType() instead.
 	 */
 	@Deprecated
 	BlackboardArtifact.Type addBlackboardArtifactType(String artifactTypeName, String displayName, BlackboardArtifact.Category category) throws TskCoreException, TskDataException {
-		if (typeNameToArtifactTypeMap.containsKey(artifactTypeName)) {
-			return typeNameToArtifactTypeMap.get(artifactTypeName);
-		}
-		CaseDbConnection connection = null;
-		acquireSingleUserCaseWriteLock();
-		Statement s = null;
-		ResultSet rs = null;
 		try {
-			connection = connections.getConnection();
-			connection.beginTransaction();
-			s = connection.createStatement();
-			rs = connection.executeQuery(s, "SELECT artifact_type_id FROM blackboard_artifact_types WHERE type_name = '" + artifactTypeName + "'"); //NON-NLS
-			if (!rs.next()) {
-				rs.close();
-				rs = connection.executeQuery(s, "SELECT MAX(artifact_type_id) AS highest_id FROM blackboard_artifact_types");
-				int maxID = 0;
-				if (rs.next()) {
-					maxID = rs.getInt("highest_id");
-					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
-						maxID = MIN_USER_DEFINED_TYPE_ID;
-					} else {
-						maxID++;
-					}
-				}
-				connection.executeUpdate(s, "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name, category_type) VALUES ('" + maxID + "', '" + artifactTypeName + "', '" + displayName + "', " + category.getID() + " )"); //NON-NLS
-				BlackboardArtifact.Type type = new BlackboardArtifact.Type(maxID, artifactTypeName, displayName, category);
-				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
-				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
-				connection.commitTransaction();
-				return type;
-			} else {
-				throw new TskDataException("The attribute type that was added was already within the system.");
-			}
-		} catch (SQLException ex) {
-			rollbackTransaction(connection);
-			throw new TskCoreException("Error adding artifact type", ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(s);
-			closeConnection(connection);
-			releaseSingleUserCaseWriteLock();
-		}
-	}
-
-	public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardArtifact artifact) throws TskCoreException {
-		CaseDbConnection connection = null;
-		Statement statement = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			statement = connection.createStatement();
-			rs = connection.executeQuery(statement, "SELECT attrs.artifact_id AS artifact_id, "
-					+ "attrs.source AS source, attrs.context AS context, attrs.attribute_type_id AS attribute_type_id, "
-					+ "attrs.value_type AS value_type, attrs.value_byte AS value_byte, "
-					+ "attrs.value_text AS value_text, attrs.value_int32 AS value_int32, "
-					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
-					+ "types.type_name AS type_name, types.display_name AS display_name "
-					+ "FROM blackboard_attributes AS attrs, blackboard_attribute_types AS types WHERE attrs.artifact_id = " + artifact.getArtifactID()
-					+ " AND attrs.attribute_type_id = types.attribute_type_id");
-			ArrayList<BlackboardAttribute> attributes = new ArrayList<BlackboardAttribute>();
-			while (rs.next()) {
-				int attributeTypeId = rs.getInt("attribute_type_id");
-				String attributeTypeName = rs.getString("type_name");
-				BlackboardAttribute.Type attributeType;
-				if (this.typeIdToAttributeTypeMap.containsKey(attributeTypeId)) {
-					attributeType = this.typeIdToAttributeTypeMap.get(attributeTypeId);
-				} else {
-					attributeType = new BlackboardAttribute.Type(attributeTypeId, attributeTypeName,
-							rs.getString("display_name"),
-							BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getInt("value_type")));
-					this.typeIdToAttributeTypeMap.put(attributeTypeId, attributeType);
-					this.typeNameToAttributeTypeMap.put(attributeTypeName, attributeType);
-				}
-
-				final BlackboardAttribute attr = new BlackboardAttribute(
-						rs.getLong("artifact_id"),
-						attributeType,
-						rs.getString("source"),
-						rs.getString("context"),
-						rs.getInt("value_int32"),
-						rs.getLong("value_int64"),
-						rs.getDouble("value_double"),
-						rs.getString("value_text"),
-						rs.getBytes("value_byte"), this
-				);
-				attr.setParentDataSourceID(artifact.getDataSourceObjectID());
-				attributes.add(attr);
-			}
-			return attributes;
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting attributes for artifact, artifact id = " + artifact.getArtifactID(), ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(statement);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
+			return blackboard.getOrAddArtifactType(displayName, displayName, category);
+		} catch (BlackboardException ex) {
+			throw new TskCoreException("Error getting or adding artifact type with name: " + artifactTypeName, ex);
 		}
 	}
-
+	
 	/**
-	 * Get the attributes associated with the given file.
-	 *
-	 * @param file
-	 *
-	 * @return
-	 *
-	 * @throws TskCoreException
+	 * Get the list of attributes for the given artifact.
+	 * 
+	 * @param artifact The artifact to load attributes for.
+	 * 
+	 * @return The list of attributes.
+	 * 
+	 * @throws TskCoreException 
+	 * 
+	 * @deprecated Use Blackboard.getBlackboardAttributes instead
 	 */
-	ArrayList<Attribute> getFileAttributes(final AbstractFile file) throws TskCoreException {
-		CaseDbConnection connection = null;
-		Statement statement = null;
-		ResultSet rs = null;
-		acquireSingleUserCaseReadLock();
-		try {
-			connection = connections.getConnection();
-			statement = connection.createStatement();
-			rs = connection.executeQuery(statement, "SELECT attrs.id as id,  attrs.obj_id AS obj_id, "
-					+ "attrs.attribute_type_id AS attribute_type_id, "
-					+ "attrs.value_type AS value_type, attrs.value_byte AS value_byte, "
-					+ "attrs.value_text AS value_text, attrs.value_int32 AS value_int32, "
-					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
-					+ "types.type_name AS type_name, types.display_name AS display_name "
-					+ "FROM tsk_file_attributes AS attrs "
-					+ " INNER JOIN blackboard_attribute_types AS types "
-					+ " ON attrs.attribute_type_id = types.attribute_type_id "
-					+ " WHERE attrs.obj_id = " + file.getId());
-
-			ArrayList<Attribute> attributes = new ArrayList<Attribute>();
-			while (rs.next()) {
-				int attributeTypeId = rs.getInt("attribute_type_id");
-				String attributeTypeName = rs.getString("type_name");
-				BlackboardAttribute.Type attributeType;
-				if (this.typeIdToAttributeTypeMap.containsKey(attributeTypeId)) {
-					attributeType = this.typeIdToAttributeTypeMap.get(attributeTypeId);
-				} else {
-					attributeType = new BlackboardAttribute.Type(attributeTypeId, attributeTypeName,
-							rs.getString("display_name"),
-							BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getInt("value_type")));
-					this.typeIdToAttributeTypeMap.put(attributeTypeId, attributeType);
-					this.typeNameToAttributeTypeMap.put(attributeTypeName, attributeType);
-				}
-
-				final Attribute attr = new Attribute(
-						rs.getLong("id"),
-						rs.getLong("obj_id"),
-						attributeType,
-						rs.getInt("value_int32"),
-						rs.getLong("value_int64"),
-						rs.getDouble("value_double"),
-						rs.getString("value_text"),
-						rs.getBytes("value_byte"), this
-				);
-				attributes.add(attr);
-			}
-			return attributes;
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error getting attributes for file, file id = " + file.getId(), ex);
-		} finally {
-			closeResultSet(rs);
-			closeStatement(statement);
-			closeConnection(connection);
-			releaseSingleUserCaseReadLock();
-		}
+	@Deprecated
+	public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardArtifact artifact) throws TskCoreException {
+		return blackboard.getBlackboardAttributes(artifact);
 	}
 
+
 	/**
 	 * Get all attributes that match a where clause. The clause should begin
 	 * with "WHERE" or "JOIN". To use this method you must know the database
@@ -5254,11 +4807,11 @@ public ArrayList<BlackboardAttribute> getMatchingAttributes(String whereClause)
 					+ "blackboard_attributes.value_text AS value_text, blackboard_attributes.value_int32 AS value_int32, "
 					+ "blackboard_attributes.value_int64 AS value_int64, blackboard_attributes.value_double AS value_double "
 					+ "FROM blackboard_attributes " + whereClause); //NON-NLS
-			ArrayList<BlackboardAttribute> matches = new ArrayList<BlackboardAttribute>();
+			ArrayList<BlackboardAttribute> matches = new ArrayList<>();
 			while (rs.next()) {
 				BlackboardAttribute.Type type;
 				// attribute type is cached, so this does not necessarily call to the db
-				type = this.getAttributeType(rs.getInt("attribute_type_id"));
+				type = blackboard.getAttributeType(rs.getInt("attribute_type_id"));
 				BlackboardAttribute attr = new BlackboardAttribute(
 						rs.getLong("artifact_id"),
 						type,
@@ -5305,7 +4858,7 @@ public ArrayList<BlackboardArtifact> getMatchingArtifacts(String whereClause) th
 			List<Long> analysisArtifactObjIds = new ArrayList<>();
 			List<Long> dataArtifactObjIds = new ArrayList<>();
 			while (resultSet.next()) {
-				BlackboardArtifact.Type type = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type type = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 					analysisArtifactObjIds.add(resultSet.getLong("artifact_obj_id"));
 				} else {
@@ -5346,7 +4899,7 @@ public ArrayList<BlackboardArtifact> getMatchingArtifacts(String whereClause) th
 	 */
 	@Deprecated
 	public BlackboardArtifact newBlackboardArtifact(int artifactTypeID, long obj_id) throws TskCoreException {
-		BlackboardArtifact.Type type = getArtifactType(artifactTypeID);
+		BlackboardArtifact.Type type = blackboard.getArtifactType(artifactTypeID);
 		if (type == null) {
 			throw new TskCoreException("Unknown artifact type for id: " + artifactTypeID);
 		}
@@ -5410,7 +4963,7 @@ public BlackboardArtifact newBlackboardArtifact(ARTIFACT_TYPE artifactType, long
 	@Deprecated
 	@SuppressWarnings("deprecation")
 	BlackboardArtifact newBlackboardArtifact(int artifactTypeID, long obj_id, long data_source_obj_id) throws TskCoreException {
-		BlackboardArtifact.Type type = getArtifactType(artifactTypeID);
+		BlackboardArtifact.Type type = blackboard.getArtifactType(artifactTypeID);
 		try (CaseDbConnection connection = connections.getConnection()) {
 			return newBlackboardArtifact(artifactTypeID, obj_id, type.getTypeName(), type.getDisplayName(), data_source_obj_id, connection);
 		}
@@ -5466,7 +5019,7 @@ PreparedStatement createInsertArtifactStatement(int artifact_type_id, long obj_i
 	 */
 	@Deprecated
 	private BlackboardArtifact newBlackboardArtifact(int artifact_type_id, long obj_id, String artifactTypeName, String artifactDisplayName, long data_source_obj_id, CaseDbConnection connection) throws TskCoreException {
-		BlackboardArtifact.Type type = getArtifactType(artifact_type_id);
+		BlackboardArtifact.Type type = blackboard.getArtifactType(artifact_type_id);
 		try {
 			if (type.getCategory() == BlackboardArtifact.Category.ANALYSIS_RESULT) {
 				return blackboard.newAnalysisResult(type, obj_id, data_source_obj_id, Score.SCORE_UNKNOWN, null, null, null, Collections.emptyList()).getAnalysisResult();
@@ -6290,7 +5843,7 @@ public BlackboardArtifact getArtifactById(long id) throws TskCoreException {
 			}
 
 			// based on the artifact type category, get the analysis result or the data artifact
-			BlackboardArtifact.Type artifactType = getArtifactType(rs.getInt("artifact_type_id"));
+			BlackboardArtifact.Type artifactType = blackboard.getArtifactType(rs.getInt("artifact_type_id"));
 			switch (artifactType.getCategory()) {
 				case ANALYSIS_RESULT:
 					return blackboard.getAnalysisResultById(id);
@@ -6331,7 +5884,7 @@ public BlackboardArtifact getArtifactByArtifactId(long id) throws TskCoreExcepti
 				Statement statement = connection.createStatement();
 				ResultSet resultSet = statement.executeQuery(query);) {
 			if (resultSet != null && resultSet.next()) {
-				BlackboardArtifact.Type artifactType = this.getArtifactType(resultSet.getInt("artifact_type_id"));
+				BlackboardArtifact.Type artifactType = blackboard.getArtifactType(resultSet.getInt("artifact_type_id"));
 				long artifactObjId = resultSet.getLong("artifact_obj_id");
 				switch (artifactType.getCategory()) {
 					case ANALYSIS_RESULT:
-- 
GitLab