1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2011-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *	 http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.datamodel;
20 
21 import com.google.common.collect.ImmutableSet;
22 import com.google.common.eventbus.EventBus;
23 import com.mchange.v2.c3p0.ComboPooledDataSource;
24 import com.mchange.v2.c3p0.DataSources;
25 import com.mchange.v2.c3p0.PooledDataSource;
26 import com.zaxxer.sparsebits.SparseBitSet;
27 import java.beans.PropertyVetoException;
28 import java.io.BufferedInputStream;
29 import java.io.BufferedOutputStream;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.io.UnsupportedEncodingException;
37 import java.net.InetAddress;
38 import java.net.URLEncoder;
39 import java.nio.charset.StandardCharsets;
40 import java.nio.file.Paths;
41 import java.sql.Connection;
42 import java.sql.DriverManager;
43 import java.sql.PreparedStatement;
44 import java.sql.ResultSet;
45 import java.sql.SQLException;
46 import java.sql.Statement;
47 import java.text.SimpleDateFormat;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.Date;
53 import java.util.EnumMap;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.LinkedHashMap;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.MissingResourceException;
60 import java.util.ResourceBundle;
61 import java.util.Set;
62 import java.util.UUID;
63 import java.util.concurrent.ConcurrentHashMap;
64 import java.util.concurrent.locks.ReentrantReadWriteLock;
65 import java.util.logging.Level;
66 import java.util.logging.Logger;
67 import org.postgresql.util.PSQLState;
68 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
69 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
70 import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE;
71 import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
72 import org.sleuthkit.datamodel.IngestModuleInfo.IngestModuleType;
73 import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess;
74 import org.sleuthkit.datamodel.TskData.DbType;
75 import org.sleuthkit.datamodel.TskData.FileKnown;
76 import org.sleuthkit.datamodel.TskData.ObjectType;
77 import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
78 import org.sleuthkit.datamodel.TskData.TSK_FS_ATTR_TYPE_ENUM;
79 import org.sleuthkit.datamodel.TskData.TSK_FS_META_FLAG_ENUM;
80 import org.sleuthkit.datamodel.TskData.TSK_FS_META_TYPE_ENUM;
81 import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
82 import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_TYPE_ENUM;
83 import org.sqlite.SQLiteConfig;
84 import org.sqlite.SQLiteDataSource;
85 import org.sqlite.SQLiteJDBCLoader;
86 
87 /**
88  * Represents the case database with methods that provide abstractions for
89  * database operations.
90  */
91 public class SleuthkitCase {
92 
93 	private static final int MAX_DB_NAME_LEN_BEFORE_TIMESTAMP = 47;
94 
95 	/**
96 	 * This must be the same as TSK_SCHEMA_VER and TSK_SCHEMA_MINOR_VER in
97 	 * tsk/auto/tsk_db.h.
98 	 */
99 	private static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION
100 			= new CaseDbSchemaVersionNumber(8, 3);
101 
102 	private static final long BASE_ARTIFACT_ID = Long.MIN_VALUE; // Artifact ids will start at the lowest negative value
103 	private static final Logger logger = Logger.getLogger(SleuthkitCase.class.getName());
104 	private static final ResourceBundle bundle = ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle");
105 	private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
106 	private static final String SQL_ERROR_CONNECTION_GROUP = "08";
107 	private static final String SQL_ERROR_AUTHENTICATION_GROUP = "28";
108 	private static final String SQL_ERROR_PRIVILEGE_GROUP = "42";
109 	private static final String SQL_ERROR_RESOURCE_GROUP = "53";
110 	private static final String SQL_ERROR_LIMIT_GROUP = "54";
111 	private static final String SQL_ERROR_INTERNAL_GROUP = "xx";
112 	private static final int MIN_USER_DEFINED_TYPE_ID = 10000;
113 
114 	private static final Set<String> CORE_TABLE_NAMES = ImmutableSet.of(
115 			"tsk_events",
116 			"tsk_event_descriptions",
117 			"tsk_event_types",
118 			"tsk_db_info",
119 			"tsk_objects",
120 			"tsk_image_info",
121 			"tsk_image_names",
122 			"tsk_vs_info",
123 			"tsk_vs_parts",
124 			"tsk_fs_info",
125 			"tsk_file_layout",
126 			"tsk_files",
127 			"tsk_files_path",
128 			"tsk_files_derived",
129 			"tsk_files_derived_method",
130 			"tag_names",
131 			"content_tags",
132 			"blackboard_artifact_tags",
133 			"blackboard_artifacts",
134 			"blackboard_attributes",
135 			"blackboard_artifact_types",
136 			"blackboard_attribute_types",
137 			"data_source_info",
138 			"file_encoding_types",
139 			"ingest_module_types",
140 			"ingest_job_status_types",
141 			"ingest_modules",
142 			"ingest_jobs",
143 			"ingest_job_modules",
144 			"account_types",
145 			"accounts",
146 			"account_relationships",
147 			"review_statuses",
148 			"reports,");
149 
150 	private static final Set<String> CORE_INDEX_NAMES = ImmutableSet.of(
151 			"parObjId",
152 			"layout_objID",
153 			"artifact_objID",
154 			"artifact_artifact_objID",
155 			"artifact_typeID",
156 			"attrsArtifactID",
157 			"mime_type",
158 			"file_extension",
159 			"relationships_account1",
160 			"relationships_account2",
161 			"relationships_relationship_source_obj_id",
162 			"relationships_date_time",
163 			"relationships_relationship_type",
164 			"relationships_data_source_obj_id",
165 			"events_time",
166 			"events_type",
167 			"events_data_source_obj_id",
168 			"events_file_obj_id",
169 			"events_artifact_id");
170 
171 	private static final String TSK_VERSION_KEY = "TSK_VER";
172 	private static final String SCHEMA_MAJOR_VERSION_KEY = "SCHEMA_MAJOR_VERSION";
173 	private static final String SCHEMA_MINOR_VERSION_KEY = "SCHEMA_MINOR_VERSION";
174 	private static final String CREATION_SCHEMA_MAJOR_VERSION_KEY = "CREATION_SCHEMA_MAJOR_VERSION";
175 	private static final String CREATION_SCHEMA_MINOR_VERSION_KEY = "CREATION_SCHEMA_MINOR_VERSION";
176 
177 	private final ConnectionPool connections;
178 	private final Map<Long, VirtualDirectory> rootIdsToCarvedFileDirs = new HashMap<>();
179 	private final Map<Long, FileSystem> fileSystemIdMap = new HashMap<>(); // Cache for file system files.
180 	private final List<ErrorObserver> sleuthkitCaseErrorObservers = new ArrayList<>();
181 	private final String databaseName;
182 	private final String dbPath;
183 	private final DbType dbType;
184 	private final String caseDirPath;
185 	private SleuthkitJNI.CaseDbHandle caseHandle;
186 	private String dbBackupPath;
187 	private Map<Integer, BlackboardArtifact.Type> typeIdToArtifactTypeMap;
188 	private Map<Integer, BlackboardAttribute.Type> typeIdToAttributeTypeMap;
189 	private Map<String, BlackboardArtifact.Type> typeNameToArtifactTypeMap;
190 	private Map<String, BlackboardAttribute.Type> typeNameToAttributeTypeMap;
191 	private CaseDbSchemaVersionNumber caseDBSchemaCreationVersion;
192 
193 	/*
194 	 * First parameter is used to specify the SparseBitSet to use, as object IDs
195 	 * can be larger than the max size of a SparseBitSet
196 	 */
197 	private final Map<Long, SparseBitSet> hasChildrenBitSetMap = new HashMap<>();
198 
199 	private long nextArtifactId; // Used to ensure artifact ids come from the desired range.
200 	// This read/write lock is used to implement a layer of locking on top of
201 	// the locking protocol provided by the underlying SQLite database. The Java
202 	// locking protocol improves performance for reasons that are not currently
203 	// understood. Note that the lock is contructed to use a fairness policy.
204 	private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
205 
206 	private CommunicationsManager communicationsMgr;
207 	private TimelineManager timelineMgr;
208 	private Blackboard blackboard;
209 	private CaseDbAccessManager dbAccessManager;
210 
211 	private final Map<String, Set<Long>> deviceIdToDatasourceObjIdMap = new HashMap<>();
212 
213 	private final EventBus eventBus = new EventBus("SleuthkitCase-EventBus");
214 
registerForEvents(Object listener)215 	public void registerForEvents(Object listener) {
216 		eventBus.register(listener);
217 	}
218 
unregisterForEvents(Object listener)219 	public void unregisterForEvents(Object listener) {
220 		eventBus.unregister(listener);
221 	}
222 
fireTSKEvent(Object event)223 	void fireTSKEvent(Object event) {
224 		eventBus.post(event);
225 	}
226 
227 	// Cache of frequently used content objects (e.g. data source, file system).
228 	private final Map<Long, Content> frequentlyUsedContentMap = new HashMap<>();
229 
230 	private Examiner cachedCurrentExaminer = null;
231 
232 	/**
233 	 * Attempts to connect to the database with the passed in settings, throws
234 	 * if the settings are not sufficient to connect to the database type
235 	 * indicated. Only attempts to connect to remote databases.
236 	 *
237 	 * When issues occur, it attempts to diagnose them by looking at the
238 	 * exception messages, returning the appropriate user-facing text for the
239 	 * exception received. This method expects the Exceptions messages to be in
240 	 * English and compares against English text.
241 	 *
242 	 * @param info The connection information
243 	 *
244 	 * @throws org.sleuthkit.datamodel.TskCoreException
245 	 */
tryConnect(CaseDbConnectionInfo info)246 	public static void tryConnect(CaseDbConnectionInfo info) throws TskCoreException {
247 		// Check if we can talk to the database.
248 		if (info.getHost() == null || info.getHost().isEmpty()) {
249 			throw new TskCoreException(bundle.getString("DatabaseConnectionCheck.MissingHostname")); //NON-NLS
250 		} else if (info.getPort() == null || info.getPort().isEmpty()) {
251 			throw new TskCoreException(bundle.getString("DatabaseConnectionCheck.MissingPort")); //NON-NLS
252 		} else if (info.getUserName() == null || info.getUserName().isEmpty()) {
253 			throw new TskCoreException(bundle.getString("DatabaseConnectionCheck.MissingUsername")); //NON-NLS
254 		} else if (info.getPassword() == null || info.getPassword().isEmpty()) {
255 			throw new TskCoreException(bundle.getString("DatabaseConnectionCheck.MissingPassword")); //NON-NLS
256 		}
257 
258 		try {
259 			Class.forName("org.postgresql.Driver"); //NON-NLS
260 			Connection conn = DriverManager.getConnection("jdbc:postgresql://" + info.getHost() + ":" + info.getPort() + "/postgres", info.getUserName(), info.getPassword()); //NON-NLS
261 			if (conn != null) {
262 				conn.close();
263 			}
264 		} catch (SQLException ex) {
265 			String result;
266 			String sqlState = ex.getSQLState().toLowerCase();
267 			if (sqlState.startsWith(SQL_ERROR_CONNECTION_GROUP)) {
268 				try {
269 					if (InetAddress.getByName(info.getHost()).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
270 						// if we can reach the host, then it's probably port problem
271 						result = bundle.getString("DatabaseConnectionCheck.Port"); //NON-NLS
272 					} else {
273 						result = bundle.getString("DatabaseConnectionCheck.HostnameOrPort"); //NON-NLS
274 					}
275 				} catch (IOException | MissingResourceException any) {
276 					// it may be anything
277 					result = bundle.getString("DatabaseConnectionCheck.Everything"); //NON-NLS
278 				}
279 			} else if (sqlState.startsWith(SQL_ERROR_AUTHENTICATION_GROUP)) {
280 				result = bundle.getString("DatabaseConnectionCheck.Authentication"); //NON-NLS
281 			} else if (sqlState.startsWith(SQL_ERROR_PRIVILEGE_GROUP)) {
282 				result = bundle.getString("DatabaseConnectionCheck.Access"); //NON-NLS
283 			} else if (sqlState.startsWith(SQL_ERROR_RESOURCE_GROUP)) {
284 				result = bundle.getString("DatabaseConnectionCheck.ServerDiskSpace"); //NON-NLS
285 			} else if (sqlState.startsWith(SQL_ERROR_LIMIT_GROUP)) {
286 				result = bundle.getString("DatabaseConnectionCheck.ServerRestart"); //NON-NLS
287 			} else if (sqlState.startsWith(SQL_ERROR_INTERNAL_GROUP)) {
288 				result = bundle.getString("DatabaseConnectionCheck.InternalServerIssue"); //NON-NLS
289 			} else {
290 				result = bundle.getString("DatabaseConnectionCheck.Connection"); //NON-NLS
291 			}
292 			throw new TskCoreException(result);
293 		} catch (ClassNotFoundException ex) {
294 			throw new TskCoreException(bundle.getString("DatabaseConnectionCheck.Installation")); //NON-NLS
295 		}
296 	}
297 
298 	/**
299 	 * Private constructor, clients must use newCase() or openCase() method to
300 	 * create an instance of this class.
301 	 *
302 	 * @param dbPath     The full path to a SQLite case database file.
303 	 * @param caseHandle A handle to a case database object in the native code
304 	 *                   SleuthKit layer.
305 	 * @param dbType     The type of database we're dealing with
306 	 *
307 	 * @throws Exception
308 	 */
SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbType dbType)309 	private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbType dbType) throws Exception {
310 		Class.forName("org.sqlite.JDBC");
311 		this.dbPath = dbPath;
312 		this.dbType = dbType;
313 		File dbFile = new File(dbPath);
314 		this.caseDirPath = dbFile.getParentFile().getAbsolutePath();
315 		this.databaseName = dbFile.getName();
316 		this.connections = new SQLiteConnections(dbPath);
317 		this.caseHandle = caseHandle;
318 		init();
319 		logSQLiteJDBCDriverInfo();
320 	}
321 
322 	/**
323 	 * Private constructor, clients must use newCase() or openCase() method to
324 	 * create an instance of this class.
325 	 *
326 	 * @param host        The PostgreSQL database server.
327 	 * @param port        The port to use connect to the PostgreSQL database
328 	 *                    server.
329 	 * @param dbName      The name of the case database.
330 	 * @param userName    The user name to use to connect to the case database.
331 	 * @param password    The password to use to connect to the case database.
332 	 * @param caseHandle  A handle to a case database object in the native code
333 	 * @param dbType      The type of database we're dealing with SleuthKit
334 	 *                    layer.
335 	 * @param caseDirPath The path to the root case directory.
336 	 *
337 	 * @throws Exception
338 	 */
SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType)339 	private SleuthkitCase(String host, int port, String dbName, String userName, String password, SleuthkitJNI.CaseDbHandle caseHandle, String caseDirPath, DbType dbType) throws Exception {
340 		this.dbPath = "";
341 		this.databaseName = dbName;
342 		this.dbType = dbType;
343 		this.caseDirPath = caseDirPath;
344 		this.connections = new PostgreSQLConnections(host, port, dbName, userName, password);
345 		this.caseHandle = caseHandle;
346 		init();
347 	}
348 
init()349 	private void init() throws Exception {
350 		typeIdToArtifactTypeMap = new ConcurrentHashMap<>();
351 		typeIdToAttributeTypeMap = new ConcurrentHashMap<>();
352 		typeNameToArtifactTypeMap = new ConcurrentHashMap<>();
353 		typeNameToAttributeTypeMap = new ConcurrentHashMap<>();
354 
355 		/*
356 		 * The following methods need to be called before updateDatabaseSchema
357 		 * due to the way that updateFromSchema2toSchema3 was implemented.
358 		 */
359 		initBlackboardArtifactTypes();
360 		initBlackboardAttributeTypes();
361 		initNextArtifactId();
362 		updateDatabaseSchema(null);
363 
364 		try (CaseDbConnection connection = connections.getConnection()) {
365 			initIngestModuleTypes(connection);
366 			initIngestStatusTypes(connection);
367 			initReviewStatuses(connection);
368 			initEncodingTypes(connection);
369 			populateHasChildrenMap(connection);
370 			updateExaminers(connection);
371 			initDBSchemaCreationVersion(connection);
372 		}
373 
374 		blackboard = new Blackboard(this);
375 		communicationsMgr = new CommunicationsManager(this);
376 		timelineMgr = new TimelineManager(this);
377 		dbAccessManager = new CaseDbAccessManager(this);
378 	}
379 
380 	/**
381 	 * Returns a set of core table names in the SleuthKit Case database.
382 	 *
383 	 * @return set of core table names
384 	 */
getCoreTableNames()385 	static Set<String> getCoreTableNames() {
386 		return CORE_TABLE_NAMES;
387 	}
388 
389 	/**
390 	 * Returns a set of core index names in the SleuthKit case database.
391 	 *
392 	 * @return set of core index names
393 	 */
getCoreIndexNames()394 	static Set<String> getCoreIndexNames() {
395 		return CORE_INDEX_NAMES;
396 	}
397 
398 	/**
399 	 * Use the internal map to determine whether the content object has children
400 	 * (of any type).
401 	 *
402 	 * @param content
403 	 *
404 	 * @return true if the content has children, false otherwise
405 	 */
getHasChildren(Content content)406 	boolean getHasChildren(Content content) {
407 		long objId = content.getId();
408 		long mapIndex = objId / Integer.MAX_VALUE;
409 		int mapValue = (int) (objId % Integer.MAX_VALUE);
410 
411 		synchronized (hasChildrenBitSetMap) {
412 			if (hasChildrenBitSetMap.containsKey(mapIndex)) {
413 				return hasChildrenBitSetMap.get(mapIndex).get(mapValue);
414 			}
415 			return false;
416 		}
417 	}
418 
419 	/**
420 	 * Add this objId to the list of objects that have children (of any type)
421 	 *
422 	 * @param objId
423 	 */
setHasChildren(Long objId)424 	private void setHasChildren(Long objId) {
425 		long mapIndex = objId / Integer.MAX_VALUE;
426 		int mapValue = (int) (objId % Integer.MAX_VALUE);
427 
428 		synchronized (hasChildrenBitSetMap) {
429 			if (hasChildrenBitSetMap.containsKey(mapIndex)) {
430 				hasChildrenBitSetMap.get(mapIndex).set(mapValue);
431 			} else {
432 				SparseBitSet bitSet = new SparseBitSet();
433 				bitSet.set(mapValue);
434 				hasChildrenBitSetMap.put(mapIndex, bitSet);
435 			}
436 		}
437 	}
438 
439 	/**
440 	 * Gets the communications manager for this case.
441 	 *
442 	 * @return The per case CommunicationsManager object.
443 	 *
444 	 * @throws org.sleuthkit.datamodel.TskCoreException
445 	 */
getCommunicationsManager()446 	public CommunicationsManager getCommunicationsManager() throws TskCoreException {
447 		return communicationsMgr;
448 	}
449 
450 	/**
451 	 * Gets the artifacts blackboard for this case.
452 	 *
453 	 * @return The per case Blackboard object.
454 	 */
getBlackboard()455 	public Blackboard getBlackboard() {
456 		return blackboard;
457 	}
458 
459 	/**
460 	 * Gets the communications manager for this case.
461 	 *
462 	 * @return The per case TimelineManager object.
463 	 *
464 	 * @throws org.sleuthkit.datamodel.TskCoreException
465 	 */
getTimelineManager()466 	public TimelineManager getTimelineManager() throws TskCoreException {
467 		return timelineMgr;
468 	}
469 
470 	/*
471 	 * Gets the case database access manager for this case.
472 	 *
473 	 * @return The per case CaseDbAccessManager object.
474 	 *
475 	 * @throws org.sleuthkit.datamodel.TskCoreException
476 	 */
getCaseDbAccessManager()477 	public synchronized CaseDbAccessManager getCaseDbAccessManager() throws TskCoreException {
478 		return dbAccessManager;
479 	}
480 
481 	/**
482 	 * Make sure the predefined artifact types are in the artifact types table.
483 	 *
484 	 * @throws SQLException
485 	 * @throws TskCoreException
486 	 */
initBlackboardArtifactTypes()487 	private void initBlackboardArtifactTypes() throws SQLException, TskCoreException {
488 		CaseDbConnection connection = connections.getConnection();
489 		Statement statement = null;
490 		ResultSet resultSet = null;
491 		acquireSingleUserCaseWriteLock();
492 		try {
493 			statement = connection.createStatement();
494 			for (ARTIFACT_TYPE type : ARTIFACT_TYPE.values()) {
495 				try {
496 					statement.execute("INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name) VALUES (" + type.getTypeID() + " , '" + type.getLabel() + "', '" + type.getDisplayName() + "')"); //NON-NLS
497 				} catch (SQLException ex) {
498 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) AS count FROM blackboard_artifact_types WHERE artifact_type_id = '" + type.getTypeID() + "'"); //NON-NLS
499 					resultSet.next();
500 					if (resultSet.getLong("count") == 0) {
501 						throw ex;
502 					}
503 					resultSet.close();
504 					resultSet = null;
505 				}
506 				this.typeIdToArtifactTypeMap.put(type.getTypeID(), new BlackboardArtifact.Type(type));
507 				this.typeNameToArtifactTypeMap.put(type.getLabel(), new BlackboardArtifact.Type(type));
508 			}
509 			if (dbType == DbType.POSTGRESQL) {
510 				int newPrimaryKeyIndex = Collections.max(Arrays.asList(ARTIFACT_TYPE.values())).getTypeID() + 1;
511 				statement.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
512 			}
513 		} finally {
514 			closeResultSet(resultSet);
515 			closeStatement(statement);
516 			connection.close();
517 			releaseSingleUserCaseWriteLock();
518 		}
519 	}
520 
521 	/**
522 	 * Make sure the predefined artifact attribute types are in the artifact
523 	 * attribute types table.
524 	 *
525 	 * @throws SQLException
526 	 * @throws TskCoreException
527 	 */
initBlackboardAttributeTypes()528 	private void initBlackboardAttributeTypes() throws SQLException, TskCoreException {
529 		CaseDbConnection connection = connections.getConnection();
530 		Statement statement = null;
531 		ResultSet resultSet = null;
532 		acquireSingleUserCaseWriteLock();
533 		try {
534 			statement = connection.createStatement();
535 			for (ATTRIBUTE_TYPE type : ATTRIBUTE_TYPE.values()) {
536 				try {
537 					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() + "')"); //NON-NLS
538 				} catch (SQLException ex) {
539 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) AS count FROM blackboard_attribute_types WHERE attribute_type_id = '" + type.getTypeID() + "'"); //NON-NLS
540 					resultSet.next();
541 					if (resultSet.getLong("count") == 0) {
542 						throw ex;
543 					}
544 					resultSet.close();
545 					resultSet = null;
546 				}
547 				this.typeIdToAttributeTypeMap.put(type.getTypeID(), new BlackboardAttribute.Type(type));
548 				this.typeNameToAttributeTypeMap.put(type.getLabel(), new BlackboardAttribute.Type(type));
549 			}
550 			if (this.dbType == DbType.POSTGRESQL) {
551 				int newPrimaryKeyIndex = Collections.max(Arrays.asList(ATTRIBUTE_TYPE.values())).getTypeID() + 1;
552 				statement.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + newPrimaryKeyIndex); //NON-NLS
553 			}
554 		} finally {
555 			closeResultSet(resultSet);
556 			closeStatement(statement);
557 			connection.close();
558 			releaseSingleUserCaseWriteLock();
559 		}
560 	}
561 
562 	/**
563 	 * Initialize the next artifact id. If there are entries in the
564 	 * blackboard_artifacts table we will use max(artifact_id) + 1 otherwise we
565 	 * will initialize the value to 0x8000000000000000 (the maximum negative
566 	 * signed long).
567 	 *
568 	 * @throws SQLException
569 	 * @throws TskCoreException
570 	 */
initNextArtifactId()571 	private void initNextArtifactId() throws SQLException, TskCoreException {
572 		CaseDbConnection connection = connections.getConnection();
573 		Statement statement = null;
574 		ResultSet resultSet = null;
575 		acquireSingleUserCaseReadLock();
576 		try {
577 			statement = connection.createStatement();
578 			resultSet = connection.executeQuery(statement, "SELECT MAX(artifact_id) AS max_artifact_id FROM blackboard_artifacts"); //NON-NLS
579 			resultSet.next();
580 			this.nextArtifactId = resultSet.getLong("max_artifact_id") + 1;
581 			if (this.nextArtifactId == 1) {
582 				this.nextArtifactId = BASE_ARTIFACT_ID;
583 			}
584 		} finally {
585 			closeResultSet(resultSet);
586 			closeStatement(statement);
587 			connection.close();
588 			releaseSingleUserCaseReadLock();
589 		}
590 	}
591 
592 	/**
593 	 * Initialize ingest module types by adding them into the
594 	 * ingest_module_types database.
595 	 *
596 	 * @throws SQLException
597 	 * @throws TskCoreException
598 	 */
initIngestModuleTypes(CaseDbConnection connection)599 	private void initIngestModuleTypes(CaseDbConnection connection) throws SQLException, TskCoreException {
600 		Statement statement = null;
601 		ResultSet resultSet = null;
602 		acquireSingleUserCaseWriteLock();
603 		try {
604 			statement = connection.createStatement();
605 			for (IngestModuleType type : IngestModuleType.values()) {
606 				try {
607 					statement.execute("INSERT INTO ingest_module_types (type_id, type_name) VALUES (" + type.ordinal() + ", '" + type.toString() + "');"); //NON-NLS
608 				} catch (SQLException ex) {
609 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) as count FROM ingest_module_types WHERE type_id = " + type.ordinal() + ";"); //NON-NLS
610 					resultSet.next();
611 					if (resultSet.getLong("count") == 0) {
612 						throw ex;
613 					}
614 					resultSet.close();
615 					resultSet = null;
616 				}
617 			}
618 		} finally {
619 			closeResultSet(resultSet);
620 			closeStatement(statement);
621 			releaseSingleUserCaseWriteLock();
622 		}
623 	}
624 
625 	/**
626 	 * Initialize ingest status types by adding them into the
627 	 * ingest_job_status_types database.
628 	 *
629 	 * @throws SQLException
630 	 * @throws TskCoreException
631 	 */
initIngestStatusTypes(CaseDbConnection connection)632 	private void initIngestStatusTypes(CaseDbConnection connection) throws SQLException, TskCoreException {
633 		Statement statement = null;
634 		ResultSet resultSet = null;
635 		acquireSingleUserCaseWriteLock();
636 		try {
637 			statement = connection.createStatement();
638 			for (IngestJobStatusType type : IngestJobStatusType.values()) {
639 				try {
640 					statement.execute("INSERT INTO ingest_job_status_types (type_id, type_name) VALUES (" + type.ordinal() + ", '" + type.toString() + "');"); //NON-NLS
641 				} catch (SQLException ex) {
642 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) as count FROM ingest_job_status_types WHERE type_id = " + type.ordinal() + ";"); //NON-NLS
643 					resultSet.next();
644 					if (resultSet.getLong("count") == 0) {
645 						throw ex;
646 					}
647 					resultSet.close();
648 					resultSet = null;
649 				}
650 			}
651 		} finally {
652 			closeResultSet(resultSet);
653 			closeStatement(statement);
654 			releaseSingleUserCaseWriteLock();
655 		}
656 	}
657 
658 	/**
659 	 * Initialize the review statuses lookup table from the ReviewStatus enum.
660 	 *
661 	 * @throws SQLException
662 	 * @throws TskCoreException if there is an error initializing the table.
663 	 */
initReviewStatuses(CaseDbConnection connection)664 	private void initReviewStatuses(CaseDbConnection connection) throws SQLException, TskCoreException {
665 		Statement statement = null;
666 		ResultSet resultSet = null;
667 		acquireSingleUserCaseWriteLock();
668 		try {
669 			statement = connection.createStatement();
670 			for (BlackboardArtifact.ReviewStatus status : BlackboardArtifact.ReviewStatus.values()) {
671 				try {
672 					statement.execute("INSERT INTO review_statuses (review_status_id, review_status_name, display_name) " //NON-NLS
673 							+ "VALUES (" + status.getID() + ",'" + status.getName() + "','" + status.getDisplayName() + "')"); //NON-NLS
674 				} catch (SQLException ex) {
675 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) as count FROM review_statuses WHERE review_status_id = " + status.getID()); //NON-NLS
676 					resultSet.next();
677 					if (resultSet.getLong("count") == 0) {
678 						throw ex;
679 					}
680 					resultSet.close();
681 					resultSet = null;
682 				}
683 			}
684 		} finally {
685 			closeResultSet(resultSet);
686 			closeStatement(statement);
687 			releaseSingleUserCaseWriteLock();
688 		}
689 	}
690 
691 	/**
692 	 * Put the file encoding types into the table. This must be called after the
693 	 * database upgrades or the encoding_types table will not exist.
694 	 *
695 	 * @throws SQLException
696 	 * @throws TskCoreException
697 	 */
initEncodingTypes(CaseDbConnection connection)698 	private void initEncodingTypes(CaseDbConnection connection) throws SQLException, TskCoreException {
699 		Statement statement = null;
700 		ResultSet resultSet = null;
701 		acquireSingleUserCaseWriteLock();
702 		try {
703 			statement = connection.createStatement();
704 			for (TskData.EncodingType type : TskData.EncodingType.values()) {
705 				try {
706 					statement.execute("INSERT INTO file_encoding_types (encoding_type, name) VALUES (" + type.getType() + " , '" + type.name() + "')"); //NON-NLS
707 				} catch (SQLException ex) {
708 					resultSet = connection.executeQuery(statement, "SELECT COUNT(*) as count FROM file_encoding_types WHERE encoding_type = " + type.getType()); //NON-NLS
709 					resultSet.next();
710 					if (resultSet.getLong("count") == 0) {
711 						throw ex;
712 					}
713 					resultSet.close();
714 					resultSet = null;
715 				}
716 			}
717 		} finally {
718 			closeResultSet(resultSet);
719 			closeStatement(statement);
720 			releaseSingleUserCaseWriteLock();
721 		}
722 	}
723 
724 	/**
725 	 * Records the current examiner name in the tsk_examiners table
726 	 *
727 	 * @param CaseDbConnection
728 	 *
729 	 * @throws SQLException
730 	 * @throws TskCoreException
731 	 */
updateExaminers(CaseDbConnection connection)732 	private void updateExaminers(CaseDbConnection connection) throws SQLException, TskCoreException {
733 
734 		String loginName = System.getProperty("user.name");
735 		if (loginName.isEmpty()) {
736 			logger.log(Level.SEVERE, "Cannot determine logged in user name");
737 			return;
738 		}
739 
740 		acquireSingleUserCaseWriteLock();
741 		Statement statement = connection.createStatement();
742 		try {
743 			String query = "INTO tsk_examiners (login_name) VALUES ('" + loginName + "')";
744 			switch (getDatabaseType()) {
745 				case POSTGRESQL:
746 					query = "INSERT " + query + " ON CONFLICT DO NOTHING"; //NON-NLS
747 					break;
748 				case SQLITE:
749 					query = "INSERT OR IGNORE " + query;
750 					break;
751 				default:
752 					throw new TskCoreException("Unknown DB Type: " + getDatabaseType().name());
753 			}
754 
755 			statement.execute(query); //NON-NLS
756 		} catch (SQLException ex) {
757 			throw new TskCoreException("Error inserting row in tsk_examiners", ex);
758 		} finally {
759 			closeStatement(statement);
760 			releaseSingleUserCaseWriteLock();
761 		}
762 	}
763 
764 	/**
765 	 * Set up or update the hasChildren map using the tsk_objects table.
766 	 *
767 	 * @param connection
768 	 *
769 	 * @throws TskCoreException
770 	 */
populateHasChildrenMap(CaseDbConnection connection)771 	private void populateHasChildrenMap(CaseDbConnection connection) throws TskCoreException {
772 		long timestamp = System.currentTimeMillis();
773 
774 		Statement statement = null;
775 		ResultSet resultSet = null;
776 		acquireSingleUserCaseWriteLock();
777 		try {
778 			statement = connection.createStatement();
779 			resultSet = statement.executeQuery("select distinct par_obj_id from tsk_objects"); //NON-NLS
780 
781 			synchronized (hasChildrenBitSetMap) {
782 				while (resultSet.next()) {
783 					setHasChildren(resultSet.getLong("par_obj_id"));
784 				}
785 			}
786 			long delay = System.currentTimeMillis() - timestamp;
787 			logger.log(Level.INFO, "Time to initialize parent node cache: {0} ms", delay); //NON-NLS
788 		} catch (SQLException ex) {
789 			throw new TskCoreException("Error populating parent node cache", ex);
790 		} finally {
791 			closeResultSet(resultSet);
792 			closeStatement(statement);
793 			releaseSingleUserCaseWriteLock();
794 		}
795 	}
796 
797 	/**
798 	 * Add the object IDs for a new data source to the has children map. At
799 	 * present, we simply reload the entire table.
800 	 *
801 	 * @throws TskCoreException
802 	 */
addDataSourceToHasChildrenMap()803 	void addDataSourceToHasChildrenMap() throws TskCoreException {
804 
805 		CaseDbConnection connection = connections.getConnection();
806 		try {
807 			populateHasChildrenMap(connection);
808 		} finally {
809 			if (connection != null) {
810 				connection.close();
811 			}
812 		}
813 	}
814 
815 	/**
816 	 * Modify the case database to bring it up-to-date with the current version
817 	 * of the database schema.
818 	 *
819 	 * @param dbPath Path to the db file. If dbPath is null, no backup will be
820 	 *               made.
821 	 *
822 	 * @throws Exception
823 	 */
updateDatabaseSchema(String dbPath)824 	private void updateDatabaseSchema(String dbPath) throws Exception {
825 		CaseDbConnection connection = connections.getConnection();
826 		ResultSet resultSet = null;
827 		Statement statement = null;
828 		acquireSingleUserCaseWriteLock();
829 		try {
830 			connection.beginTransaction();
831 
832 			boolean hasMinorVersion = false;
833 			ResultSet columns = connection.getConnection().getMetaData().getColumns(null, null, "tsk_db_info", "schema%");
834 			while (columns.next()) {
835 				if (columns.getString("COLUMN_NAME").equals("schema_minor_ver")) {
836 					hasMinorVersion = true;
837 				}
838 			}
839 
840 			// Get the schema version number of the case database from the tsk_db_info table.
841 			int dbSchemaMajorVersion;
842 			int dbSchemaMinorVersion = 0; //schemas before 7 have no minor version , default it to zero.
843 
844 			statement = connection.createStatement();
845 			resultSet = connection.executeQuery(statement, "SELECT schema_ver"
846 					+ (hasMinorVersion ? ", schema_minor_ver" : "")
847 					+ " FROM tsk_db_info"); //NON-NLS
848 			if (resultSet.next()) {
849 				dbSchemaMajorVersion = resultSet.getInt("schema_ver"); //NON-NLS
850 				if (hasMinorVersion) {
851 					//if there is a minor version column, use it, else default to zero.
852 					dbSchemaMinorVersion = resultSet.getInt("schema_minor_ver"); //NON-NLS
853 				}
854 			} else {
855 				throw new TskCoreException();
856 			}
857 			CaseDbSchemaVersionNumber dbSchemaVersion = new CaseDbSchemaVersionNumber(dbSchemaMajorVersion, dbSchemaMinorVersion);
858 
859 			resultSet.close();
860 			resultSet = null;
861 			statement.close();
862 			statement = null;
863 			//check schema compatibility
864 			if (false == CURRENT_DB_SCHEMA_VERSION.isCompatible(dbSchemaVersion)) {
865 				//we cannot open a db with a major schema version higher than the current one.
866 				throw new TskUnsupportedSchemaVersionException(
867 						"Unsupported DB schema version " + dbSchemaVersion + ", the highest supported schema version is " + CURRENT_DB_SCHEMA_VERSION.getMajor() + ".X");
868 			} else if (dbSchemaVersion.compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
869 				//The schema version is compatible,possibly after upgrades.
870 
871 				if (null != dbPath) {
872 					// Make a backup copy of the database. Client code can get the path of the backup
873 					// using the getBackupDatabasePath() method.
874 					String backupFilePath = dbPath + ".schemaVer" + dbSchemaVersion.toString() + ".backup"; //NON-NLS
875 					copyCaseDB(backupFilePath);
876 					dbBackupPath = backupFilePath;
877 				}
878 
879 				// ***CALL SCHEMA UPDATE METHODS HERE***
880 				// Each method should examine the schema version passed to it and either:
881 				//    a. do nothing and return the schema version unchanged, or
882 				//    b. upgrade the database and return the schema version that the db was upgraded to.
883 				dbSchemaVersion = updateFromSchema2toSchema3(dbSchemaVersion, connection);
884 				dbSchemaVersion = updateFromSchema3toSchema4(dbSchemaVersion, connection);
885 				dbSchemaVersion = updateFromSchema4toSchema5(dbSchemaVersion, connection);
886 				dbSchemaVersion = updateFromSchema5toSchema6(dbSchemaVersion, connection);
887 				dbSchemaVersion = updateFromSchema6toSchema7(dbSchemaVersion, connection);
888 				dbSchemaVersion = updateFromSchema7toSchema7dot1(dbSchemaVersion, connection);
889 				dbSchemaVersion = updateFromSchema7dot1toSchema7dot2(dbSchemaVersion, connection);
890 				dbSchemaVersion = updateFromSchema7dot2toSchema8dot0(dbSchemaVersion, connection);
891 				dbSchemaVersion = updateFromSchema8dot0toSchema8dot1(dbSchemaVersion, connection);
892 				dbSchemaVersion = updateFromSchema8dot1toSchema8dot2(dbSchemaVersion, connection);
893 				dbSchemaVersion = updateFromSchema8dot2toSchema8dot3(dbSchemaVersion, connection);
894 				statement = connection.createStatement();
895 				connection.executeUpdate(statement, "UPDATE tsk_db_info SET schema_ver = " + dbSchemaVersion.getMajor() + ", schema_minor_ver = " + dbSchemaVersion.getMinor()); //NON-NLS
896 				connection.executeUpdate(statement, "UPDATE tsk_db_info_extended SET value = " + dbSchemaVersion.getMajor() + " WHERE name = '" + SCHEMA_MAJOR_VERSION_KEY + "'"); //NON-NLS
897 				connection.executeUpdate(statement, "UPDATE tsk_db_info_extended SET value = " + dbSchemaVersion.getMinor() + " WHERE name = '" + SCHEMA_MINOR_VERSION_KEY + "'"); //NON-NLS
898 				statement.close();
899 				statement = null;
900 			}
901 
902 			connection.commitTransaction();
903 		} catch (Exception ex) { // Cannot do exception multi-catch in Java 6, so use catch-all.
904 			connection.rollbackTransaction();
905 			throw ex;
906 		} finally {
907 			closeResultSet(resultSet);
908 			closeStatement(statement);
909 			connection.close();
910 			releaseSingleUserCaseWriteLock();
911 		}
912 	}
913 
914 	/**
915 	 * Get the database schema creation version from database. This must be
916 	 * called after the database upgrades or the tsk_db_info_extended table may
917 	 * not exist.
918 	 *
919 	 * @throws SQLException
920 	 */
initDBSchemaCreationVersion(CaseDbConnection connection)921 	private void initDBSchemaCreationVersion(CaseDbConnection connection) throws SQLException {
922 
923 		Statement statement = null;
924 		ResultSet resultSet = null;
925 		String createdSchemaMajorVersion = "0";
926 		String createdSchemaMinorVersion = "0";
927 		acquireSingleUserCaseReadLock();
928 		try {
929 			statement = connection.createStatement();
930 			resultSet = connection.executeQuery(statement, "SELECT name, value FROM tsk_db_info_extended");
931 			while (resultSet.next()) {
932 				String name = resultSet.getString("name");
933 				if (name.equals(CREATION_SCHEMA_MAJOR_VERSION_KEY) || name.equals("CREATED_SCHEMA_MAJOR_VERSION")) {
934 					createdSchemaMajorVersion = resultSet.getString("value");
935 				} else if (name.equals(CREATION_SCHEMA_MINOR_VERSION_KEY) || name.equals("CREATED_SCHEMA_MINOR_VERSION")) {
936 					createdSchemaMinorVersion = resultSet.getString("value");
937 				}
938 			}
939 
940 		} finally {
941 			closeResultSet(resultSet);
942 			closeStatement(statement);
943 			releaseSingleUserCaseReadLock();
944 		}
945 
946 		caseDBSchemaCreationVersion = new CaseDbSchemaVersionNumber(Integer.parseInt(createdSchemaMajorVersion), Integer.parseInt(createdSchemaMinorVersion));
947 	}
948 
949 	/**
950 	 * Make a duplicate / backup copy of the current case database. Makes a new
951 	 * copy only, and continues to use the current connection.
952 	 *
953 	 * @param newDBPath Path to the copy to be created. File will be overwritten
954 	 *                  if it exists.
955 	 *
956 	 * @throws IOException if copying fails.
957 	 */
copyCaseDB(String newDBPath)958 	public void copyCaseDB(String newDBPath) throws IOException {
959 		if (dbPath.isEmpty()) {
960 			throw new IOException("Copying case database files is not supported for this type of case database"); //NON-NLS
961 		}
962 		InputStream in = null;
963 		OutputStream out = null;
964 		acquireSingleUserCaseWriteLock();
965 		try {
966 			InputStream inFile = new FileInputStream(dbPath);
967 			in = new BufferedInputStream(inFile);
968 			OutputStream outFile = new FileOutputStream(newDBPath);
969 			out = new BufferedOutputStream(outFile);
970 			int bytesRead = in.read();
971 			while (bytesRead != -1) {
972 				out.write(bytesRead);
973 				bytesRead = in.read();
974 			}
975 		} finally {
976 			try {
977 				if (in != null) {
978 					in.close();
979 				}
980 				if (out != null) {
981 					out.flush();
982 					out.close();
983 				}
984 			} catch (IOException e) {
985 				logger.log(Level.WARNING, "Could not close streams after db copy", e); //NON-NLS
986 			}
987 			releaseSingleUserCaseWriteLock();
988 		}
989 	}
990 
991 	/**
992 	 * Write some SQLite JDBC driver details to the log file.
993 	 */
logSQLiteJDBCDriverInfo()994 	private void logSQLiteJDBCDriverInfo() {
995 		try {
996 			SleuthkitCase.logger.info(String.format("sqlite-jdbc version %s loaded in %s mode", //NON-NLS
997 					SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode()
998 					? "native" : "pure-java")); //NON-NLS
999 		} catch (Exception ex) {
1000 			SleuthkitCase.logger.log(Level.SEVERE, "Error querying case database mode", ex);
1001 		}
1002 	}
1003 
1004 	/**
1005 	 * Updates a schema version 2 database to a schema version 3 database.
1006 	 *
1007 	 * @param schemaVersion The current schema version of the database.
1008 	 * @param connection    A connection to the case database.
1009 	 *
1010 	 * @return The new database schema version.
1011 	 *
1012 	 * @throws SQLException     If there is an error completing a database
1013 	 *                          operation.
1014 	 * @throws TskCoreException If there is an error completing a database
1015 	 *                          operation via another SleuthkitCase method.
1016 	 */
1017 	@SuppressWarnings("deprecation")
updateFromSchema2toSchema3(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1018 	private CaseDbSchemaVersionNumber updateFromSchema2toSchema3(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1019 		if (schemaVersion.getMajor() != 2) {
1020 			return schemaVersion;
1021 		}
1022 		Statement statement = null;
1023 		Statement updateStatement = null;
1024 		ResultSet resultSet = null;
1025 		acquireSingleUserCaseWriteLock();
1026 		try {
1027 			statement = connection.createStatement();
1028 
1029 			// Add new tables for tags.
1030 			statement.execute("CREATE TABLE tag_names (tag_name_id INTEGER PRIMARY KEY, display_name TEXT UNIQUE, description TEXT NOT NULL, color TEXT NOT NULL)"); //NON-NLS
1031 			statement.execute("CREATE TABLE content_tags (tag_id INTEGER PRIMARY KEY, obj_id INTEGER NOT NULL, tag_name_id INTEGER NOT NULL, comment TEXT NOT NULL, begin_byte_offset INTEGER NOT NULL, end_byte_offset INTEGER NOT NULL)"); //NON-NLS
1032 			statement.execute("CREATE TABLE blackboard_artifact_tags (tag_id INTEGER PRIMARY KEY, artifact_id INTEGER NOT NULL, tag_name_id INTEGER NOT NULL, comment TEXT NOT NULL)"); //NON-NLS
1033 
1034 			// Add a new table for reports.
1035 			statement.execute("CREATE TABLE reports (report_id INTEGER PRIMARY KEY, path TEXT NOT NULL, crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL)"); //NON-NLS
1036 
1037 			// Add new columns to the image info table.
1038 			statement.execute("ALTER TABLE tsk_image_info ADD COLUMN size INTEGER;"); //NON-NLS
1039 			statement.execute("ALTER TABLE tsk_image_info ADD COLUMN md5 TEXT;"); //NON-NLS
1040 			statement.execute("ALTER TABLE tsk_image_info ADD COLUMN display_name TEXT;"); //NON-NLS
1041 
1042 			// Add a new column to the file system info table.
1043 			statement.execute("ALTER TABLE tsk_fs_info ADD COLUMN display_name TEXT;"); //NON-NLS
1044 
1045 			// Add a new column to the file table.
1046 			statement.execute("ALTER TABLE tsk_files ADD COLUMN meta_seq INTEGER;"); //NON-NLS
1047 
1048 			// Add new columns and indexes to the attributes table and populate the
1049 			// new column. Note that addition of the new column is a denormalization
1050 			// to optimize attribute queries.
1051 			statement.execute("ALTER TABLE blackboard_attributes ADD COLUMN artifact_type_id INTEGER NULL NOT NULL DEFAULT -1;"); //NON-NLS
1052 			statement.execute("CREATE INDEX attribute_artifactTypeId ON blackboard_attributes(artifact_type_id);"); //NON-NLS
1053 			statement.execute("CREATE INDEX attribute_valueText ON blackboard_attributes(value_text);"); //NON-NLS
1054 			statement.execute("CREATE INDEX attribute_valueInt32 ON blackboard_attributes(value_int32);"); //NON-NLS
1055 			statement.execute("CREATE INDEX attribute_valueInt64 ON blackboard_attributes(value_int64);"); //NON-NLS
1056 			statement.execute("CREATE INDEX attribute_valueDouble ON blackboard_attributes(value_double);"); //NON-NLS
1057 			resultSet = statement.executeQuery("SELECT attrs.artifact_id AS artifact_id, " //NON-NLS
1058 					+ "arts.artifact_type_id AS artifact_type_id " //NON-NLS
1059 					+ "FROM blackboard_attributes AS attrs " //NON-NLS
1060 					+ "INNER JOIN blackboard_artifacts AS arts " //NON-NLS
1061 					+ "WHERE attrs.artifact_id = arts.artifact_id;"); //NON-NLS
1062 			updateStatement = connection.createStatement();
1063 			while (resultSet.next()) {
1064 				long artifactId = resultSet.getLong("artifact_id");
1065 				int artifactTypeId = resultSet.getInt("artifact_type_id");
1066 				updateStatement.executeUpdate(
1067 						"UPDATE blackboard_attributes " //NON-NLS
1068 						+ "SET artifact_type_id = " + artifactTypeId //NON-NLS
1069 						+ " WHERE blackboard_attributes.artifact_id = " + artifactId + ";"); //NON-NLS
1070 			}
1071 			resultSet.close();
1072 			resultSet = null;
1073 
1074 			// Convert existing tag artifact and attribute rows to rows in the new tags tables.
1075 			// TODO: This code depends on prepared statements that could evolve with
1076 			// time, breaking this upgrade. The code that follows should be rewritten
1077 			// to do everything with SQL specific to case database schema version 2.
1078 			HashMap<String, TagName> tagNames = new HashMap<String, TagName>();
1079 			for (BlackboardArtifact artifact : getBlackboardArtifacts(ARTIFACT_TYPE.TSK_TAG_FILE)) {
1080 				Content content = getContentById(artifact.getObjectID());
1081 				String name = ""; //NON-NLS
1082 				String comment = ""; //NON-NLS
1083 				ArrayList<BlackboardAttribute> attributes = getBlackboardAttributes(artifact);
1084 				for (BlackboardAttribute attribute : attributes) {
1085 					if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID()) {
1086 						name = attribute.getValueString();
1087 					} else if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID()) {
1088 						comment = attribute.getValueString();
1089 					}
1090 				}
1091 				if (!name.isEmpty()) {
1092 					TagName tagName;
1093 					if (tagNames.containsKey(name)) {
1094 						tagName = tagNames.get(name);
1095 					} else {
1096 						tagName = addTagName(name, "", TagName.HTML_COLOR.NONE); //NON-NLS
1097 						tagNames.put(name, tagName);
1098 					}
1099 					addContentTag(content, tagName, comment, 0, content.getSize() - 1);
1100 				}
1101 			}
1102 			for (BlackboardArtifact artifact : getBlackboardArtifacts(ARTIFACT_TYPE.TSK_TAG_ARTIFACT)) {
1103 				long taggedArtifactId = -1;
1104 				String name = ""; //NON-NLS
1105 				String comment = ""; //NON-NLS
1106 				ArrayList<BlackboardAttribute> attributes = getBlackboardAttributes(artifact);
1107 				for (BlackboardAttribute attribute : attributes) {
1108 					if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_TAG_NAME.getTypeID()) {
1109 						name = attribute.getValueString();
1110 					} else if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID()) {
1111 						comment = attribute.getValueString();
1112 					} else if (attribute.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()) {
1113 						taggedArtifactId = attribute.getValueLong();
1114 					}
1115 				}
1116 				if (taggedArtifactId != -1 && !name.isEmpty()) {
1117 					TagName tagName;
1118 					if (tagNames.containsKey(name)) {
1119 						tagName = tagNames.get(name);
1120 					} else {
1121 						tagName = addTagName(name, "", TagName.HTML_COLOR.NONE); //NON-NLS
1122 						tagNames.put(name, tagName);
1123 					}
1124 					addBlackboardArtifactTag(getBlackboardArtifact(taggedArtifactId), tagName, comment);
1125 				}
1126 			}
1127 			statement.execute(
1128 					"DELETE FROM blackboard_attributes WHERE artifact_id IN " //NON-NLS
1129 					+ "(SELECT artifact_id FROM blackboard_artifacts WHERE artifact_type_id = " //NON-NLS
1130 					+ ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID()
1131 					+ " OR artifact_type_id = " + ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID() + ");"); //NON-NLS
1132 			statement.execute(
1133 					"DELETE FROM blackboard_artifacts WHERE artifact_type_id = " //NON-NLS
1134 					+ ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID()
1135 					+ " OR artifact_type_id = " + ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID() + ";"); //NON-NLS
1136 
1137 			return new CaseDbSchemaVersionNumber(3, 0);
1138 		} finally {
1139 			closeStatement(updateStatement);
1140 			closeResultSet(resultSet);
1141 			closeStatement(statement);
1142 			connection.close();
1143 			releaseSingleUserCaseWriteLock();
1144 		}
1145 	}
1146 
1147 	/**
1148 	 * Updates a schema version 3 database to a schema version 4 database.
1149 	 *
1150 	 * @param schemaVersion The current schema version of the database.
1151 	 * @param connection    A connection to the case database.
1152 	 *
1153 	 * @return The new database schema version.
1154 	 *
1155 	 * @throws SQLException     If there is an error completing a database
1156 	 *                          operation.
1157 	 * @throws TskCoreException If there is an error completing a database
1158 	 *                          operation via another SleuthkitCase method.
1159 	 */
updateFromSchema3toSchema4(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1160 	private CaseDbSchemaVersionNumber updateFromSchema3toSchema4(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1161 		if (schemaVersion.getMajor() != 3) {
1162 			return schemaVersion;
1163 		}
1164 
1165 		Statement statement = null;
1166 		ResultSet resultSet = null;
1167 		Statement queryStatement = null;
1168 		ResultSet queryResultSet = null;
1169 		Statement updateStatement = null;
1170 		acquireSingleUserCaseWriteLock();
1171 		try {
1172 			// Add mime_type column to tsk_files table. Populate with general
1173 			// info artifact file signature data.
1174 			statement = connection.createStatement();
1175 			updateStatement = connection.createStatement();
1176 			statement.execute("ALTER TABLE tsk_files ADD COLUMN mime_type TEXT;");
1177 			resultSet = statement.executeQuery("SELECT files.obj_id AS obj_id, attrs.value_text AS value_text "
1178 					+ "FROM tsk_files AS files, blackboard_attributes AS attrs, blackboard_artifacts AS arts "
1179 					+ "WHERE files.obj_id = arts.obj_id AND "
1180 					+ "arts.artifact_id = attrs.artifact_id AND "
1181 					+ "arts.artifact_type_id = 1 AND "
1182 					+ "attrs.attribute_type_id = 62");
1183 			while (resultSet.next()) {
1184 				updateStatement.executeUpdate(
1185 						"UPDATE tsk_files " //NON-NLS
1186 						+ "SET mime_type = '" + resultSet.getString("value_text") + "' " //NON-NLS
1187 						+ "WHERE tsk_files.obj_id = " + resultSet.getInt("obj_id") + ";"); //NON-NLS
1188 			}
1189 			resultSet.close();
1190 
1191 			// Add value_type column to blackboard_attribute_types table.
1192 			statement.execute("ALTER TABLE blackboard_attribute_types ADD COLUMN value_type INTEGER NOT NULL DEFAULT -1;");
1193 			resultSet = statement.executeQuery("SELECT * FROM blackboard_attribute_types AS types"); //NON-NLS
1194 			while (resultSet.next()) {
1195 				int attributeTypeId = resultSet.getInt("attribute_type_id");
1196 				String attributeLabel = resultSet.getString("type_name");
1197 				if (attributeTypeId < MIN_USER_DEFINED_TYPE_ID) {
1198 					updateStatement.executeUpdate(
1199 							"UPDATE blackboard_attribute_types " //NON-NLS
1200 							+ "SET value_type = " + ATTRIBUTE_TYPE.fromLabel(attributeLabel).getValueType().getType() + " " //NON-NLS
1201 							+ "WHERE blackboard_attribute_types.attribute_type_id = " + attributeTypeId + ";"); //NON-NLS
1202 				}
1203 			}
1204 			resultSet.close();
1205 
1206 			// Add a data_sources_info table.
1207 			queryStatement = connection.createStatement();
1208 			statement.execute("CREATE TABLE data_source_info (obj_id INTEGER PRIMARY KEY, device_id TEXT NOT NULL, time_zone TEXT NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id));");
1209 			resultSet = statement.executeQuery("SELECT * FROM tsk_objects WHERE par_obj_id IS NULL");
1210 			while (resultSet.next()) {
1211 				long objectId = resultSet.getLong("obj_id");
1212 				String timeZone = "";
1213 				queryResultSet = queryStatement.executeQuery("SELECT tzone FROM tsk_image_info WHERE obj_id = " + objectId);
1214 				if (queryResultSet.next()) {
1215 					timeZone = queryResultSet.getString("tzone");
1216 				}
1217 				queryResultSet.close();
1218 				updateStatement.executeUpdate("INSERT INTO data_source_info (obj_id, device_id, time_zone) "
1219 						+ "VALUES(" + objectId + ", '" + UUID.randomUUID().toString() + "' , '" + timeZone + "');");
1220 			}
1221 			resultSet.close();
1222 
1223 			// Add data_source_obj_id column to the tsk_files table.
1224 			//
1225 			// NOTE: A new case database will have the following FK constraint:
1226 			//
1227 			// REFERENCES data_source_info (obj_id)
1228 			//
1229 			// The constraint is sacrificed here to avoid having to create and
1230 			// populate a new tsk_files table.
1231 			//
1232 			// TODO: Do this right.
1233 			statement.execute("ALTER TABLE tsk_files ADD COLUMN data_source_obj_id BIGINT NOT NULL DEFAULT -1;");
1234 			resultSet = statement.executeQuery("SELECT tsk_files.obj_id AS obj_id, par_obj_id FROM tsk_files, tsk_objects WHERE tsk_files.obj_id = tsk_objects.obj_id");
1235 			while (resultSet.next()) {
1236 				long fileId = resultSet.getLong("obj_id");
1237 				long dataSourceId = getDataSourceObjectId(connection, fileId);
1238 				updateStatement.executeUpdate("UPDATE tsk_files SET data_source_obj_id = " + dataSourceId + " WHERE obj_id = " + fileId + ";");
1239 			}
1240 			resultSet.close();
1241 			statement.execute("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); //NON-NLS
1242 			statement.execute("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); //NON-NLS
1243 			if (this.dbType.equals(DbType.SQLITE)) {
1244 				statement.execute("CREATE TABLE ingest_modules (ingest_module_id INTEGER PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));"); //NON-NLS
1245 				statement.execute("CREATE TABLE ingest_jobs (ingest_job_id INTEGER PRIMARY KEY, obj_id BIGINT NOT NULL, host_name TEXT NOT NULL, start_date_time BIGINT NOT NULL, end_date_time BIGINT NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));"); //NON-NLS
1246 			} else {
1247 				statement.execute("CREATE TABLE ingest_modules (ingest_module_id BIGSERIAL PRIMARY KEY, display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id));"); //NON-NLS
1248 				statement.execute("CREATE TABLE ingest_jobs (ingest_job_id BIGSERIAL PRIMARY KEY, obj_id BIGINT NOT NULL, host_name TEXT NOT NULL, start_date_time BIGINT NOT NULL, end_date_time BIGINT NOT NULL, status_id INTEGER NOT NULL, settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id));"); //NON-NLS
1249 			}
1250 
1251 			statement.execute("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id), FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id));"); //NON-NLS
1252 			initIngestModuleTypes(connection);
1253 			initIngestStatusTypes(connection);
1254 
1255 			return new CaseDbSchemaVersionNumber(4, 0);
1256 
1257 		} finally {
1258 			closeResultSet(queryResultSet);
1259 			closeStatement(queryStatement);
1260 			closeStatement(updateStatement);
1261 			closeResultSet(resultSet);
1262 			closeStatement(statement);
1263 			releaseSingleUserCaseWriteLock();
1264 		}
1265 	}
1266 
1267 	/**
1268 	 * Updates a schema version 4 database to a schema version 5 database.
1269 	 *
1270 	 * @param schemaVersion The current schema version of the database.
1271 	 * @param connection    A connection to the case database.
1272 	 *
1273 	 * @return The new database schema version.
1274 	 *
1275 	 * @throws SQLException     If there is an error completing a database
1276 	 *                          operation.
1277 	 * @throws TskCoreException If there is an error completing a database
1278 	 *                          operation via another SleuthkitCase method.
1279 	 */
updateFromSchema4toSchema5(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1280 	private CaseDbSchemaVersionNumber updateFromSchema4toSchema5(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1281 		if (schemaVersion.getMajor() != 4) {
1282 			return schemaVersion;
1283 		}
1284 
1285 		Statement statement = null;
1286 		acquireSingleUserCaseWriteLock();
1287 		try {
1288 			// Add the review_statuses lookup table.
1289 			statement = connection.createStatement();
1290 			statement.execute("CREATE TABLE review_statuses (review_status_id INTEGER PRIMARY KEY, review_status_name TEXT NOT NULL, display_name TEXT NOT NULL)");
1291 
1292 			/*
1293 			 * Add review_status_id column to artifacts table.
1294 			 *
1295 			 * NOTE: For DBs created with schema 5 we define a foreign key
1296 			 * constraint on the review_status_column. We don't bother with this
1297 			 * for DBs updated to schema 5 because of limitations of the SQLite
1298 			 * ALTER TABLE command.
1299 			 */
1300 			statement.execute("ALTER TABLE blackboard_artifacts ADD COLUMN review_status_id INTEGER NOT NULL DEFAULT " + BlackboardArtifact.ReviewStatus.UNDECIDED.getID());
1301 
1302 			// Add the encoding table
1303 			statement.execute("CREATE TABLE file_encoding_types (encoding_type INTEGER PRIMARY KEY, name TEXT NOT NULL);");
1304 			initEncodingTypes(connection);
1305 
1306 			/*
1307 			 * This needs to be done due to a Autopsy/TSK out of synch problem.
1308 			 * Without this, it is possible to upgrade from version 4 to 5 and
1309 			 * then 5 to 6, but not from 4 to 6.
1310 			 */
1311 			initReviewStatuses(connection);
1312 
1313 			// Add encoding type column to tsk_files_path
1314 			// This should really have the FOREIGN KEY constraint but there are problems
1315 			// getting that to work, so we don't add it on this upgrade path.
1316 			statement.execute("ALTER TABLE tsk_files_path ADD COLUMN encoding_type INTEGER NOT NULL DEFAULT 0;");
1317 
1318 			return new CaseDbSchemaVersionNumber(5, 0);
1319 
1320 		} finally {
1321 			closeStatement(statement);
1322 			releaseSingleUserCaseWriteLock();
1323 		}
1324 	}
1325 
1326 	/**
1327 	 * Updates a schema version 5 database to a schema version 6 database.
1328 	 *
1329 	 * @param schemaVersion The current schema version of the database.
1330 	 * @param connection    A connection to the case database.
1331 	 *
1332 	 * @return The new database schema version.
1333 	 *
1334 	 * @throws SQLException     If there is an error completing a database
1335 	 *                          operation.
1336 	 * @throws TskCoreException If there is an error completing a database
1337 	 *                          operation via another SleuthkitCase method.
1338 	 */
updateFromSchema5toSchema6(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1339 	private CaseDbSchemaVersionNumber updateFromSchema5toSchema6(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1340 		if (schemaVersion.getMajor() != 5) {
1341 			return schemaVersion;
1342 		}
1343 
1344 		/*
1345 		 * This upgrade fixes a bug where some releases had artifact review
1346 		 * status support in the case database and others did not.
1347 		 */
1348 		Statement statement = null;
1349 		ResultSet resultSet = null;
1350 		acquireSingleUserCaseWriteLock();
1351 		try {
1352 			/*
1353 			 * Add the review_statuses lookup table, if missing.
1354 			 */
1355 			statement = connection.createStatement();
1356 			statement.execute("CREATE TABLE IF NOT EXISTS review_statuses (review_status_id INTEGER PRIMARY KEY, review_status_name TEXT NOT NULL, display_name TEXT NOT NULL)");
1357 
1358 			resultSet = connection.executeQuery(statement, "SELECT COUNT(*) AS count FROM review_statuses"); //NON-NLS
1359 			resultSet.next();
1360 			if (resultSet.getLong("count") == 0) {
1361 				/*
1362 				 * Add review_status_id column to artifacts table.
1363 				 *
1364 				 * NOTE: For DBs created with schema 5 or 6 we define a foreign
1365 				 * key constraint on the review_status_column. We don't bother
1366 				 * with this for DBs updated to schema 5 or 6 because of
1367 				 * limitations of the SQLite ALTER TABLE command.
1368 				 */
1369 				statement.execute("ALTER TABLE blackboard_artifacts ADD COLUMN review_status_id INTEGER NOT NULL DEFAULT " + BlackboardArtifact.ReviewStatus.UNDECIDED.getID());
1370 			}
1371 
1372 			return new CaseDbSchemaVersionNumber(6, 0);
1373 
1374 		} finally {
1375 			closeResultSet(resultSet);
1376 			closeStatement(statement);
1377 			releaseSingleUserCaseWriteLock();
1378 		}
1379 	}
1380 
1381 	/**
1382 	 * Updates a schema version 6 database to a schema version 7 database.
1383 	 *
1384 	 * @param schemaVersion The current schema version of the database.
1385 	 * @param connection    A connection to the case database.
1386 	 *
1387 	 * @return The new database schema version.
1388 	 *
1389 	 * @throws SQLException     If there is an error completing a database
1390 	 *                          operation.
1391 	 * @throws TskCoreException If there is an error completing a database
1392 	 *                          operation via another SleuthkitCase method.
1393 	 */
updateFromSchema6toSchema7(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1394 	private CaseDbSchemaVersionNumber updateFromSchema6toSchema7(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1395 		if (schemaVersion.getMajor() != 6) {
1396 			return schemaVersion;
1397 		}
1398 
1399 		/*
1400 		 * This upgrade adds an indexed extension column to the tsk_files table.
1401 		 */
1402 		Statement statement = null;
1403 		Statement updstatement = null;
1404 		ResultSet resultSet = null;
1405 		acquireSingleUserCaseWriteLock();
1406 		try {
1407 			statement = connection.createStatement();
1408 			updstatement = connection.createStatement();
1409 			statement.execute("ALTER TABLE tsk_files ADD COLUMN extension TEXT");
1410 
1411 			resultSet = connection.executeQuery(statement, "SELECT obj_id,name FROM tsk_files"); //NON-NLS
1412 			while (resultSet.next()) {
1413 				long objID = resultSet.getLong("obj_id");
1414 				String name = resultSet.getString("name");
1415 				updstatement.executeUpdate("UPDATE tsk_files SET extension = '" + escapeSingleQuotes(extractExtension(name)) + "' "
1416 						+ "WHERE obj_id = " + objID);
1417 			}
1418 
1419 			statement.execute("CREATE INDEX file_extension ON tsk_files ( extension )");
1420 
1421 			// Add artifact_obj_id column to blackboard_artifacts table, data conversion for old versions isn't necesarry.
1422 			statement.execute("ALTER TABLE blackboard_artifacts ADD COLUMN artifact_obj_id INTEGER NOT NULL DEFAULT -1");
1423 
1424 			return new CaseDbSchemaVersionNumber(7, 0);
1425 
1426 		} finally {
1427 			closeResultSet(resultSet);
1428 			closeStatement(statement);
1429 			closeStatement(updstatement);
1430 			releaseSingleUserCaseWriteLock();
1431 		}
1432 	}
1433 
1434 	/**
1435 	 * Updates a schema version 7 database to a schema version 7.1 database.
1436 	 *
1437 	 * @param schemaVersion The current schema version of the database.
1438 	 * @param connection    A connection to the case database.
1439 	 *
1440 	 * @return The new database schema version.
1441 	 *
1442 	 * @throws SQLException     If there is an error completing a database
1443 	 *                          operation.
1444 	 * @throws TskCoreException If there is an error completing a database
1445 	 *                          operation via another SleuthkitCase method.
1446 	 */
updateFromSchema7toSchema7dot1(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1447 	private CaseDbSchemaVersionNumber updateFromSchema7toSchema7dot1(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1448 		if (schemaVersion.getMajor() != 7) {
1449 			return schemaVersion;
1450 		}
1451 
1452 		if (schemaVersion.getMinor() != 0) {
1453 			return schemaVersion;
1454 		}
1455 
1456 		/*
1457 		 * This upgrade adds a minor version number column.
1458 		 */
1459 		Statement statement = null;
1460 		ResultSet resultSet = null;
1461 		acquireSingleUserCaseWriteLock();
1462 		try {
1463 			statement = connection.createStatement();
1464 
1465 			//add the schema minor version number column.
1466 			if (schemaVersion.getMinor() == 0) {
1467 				//add the schema minor version number column.
1468 				statement.execute("ALTER TABLE tsk_db_info ADD COLUMN schema_minor_ver INTEGER DEFAULT 1");
1469 			}
1470 			return new CaseDbSchemaVersionNumber(7, 1);
1471 
1472 		} finally {
1473 			closeResultSet(resultSet);
1474 			closeStatement(statement);
1475 			releaseSingleUserCaseWriteLock();
1476 		}
1477 	}
1478 
1479 	/**
1480 	 * Updates a schema version 7.1 database to a schema version 7.2 database.
1481 	 *
1482 	 * @param schemaVersion The current schema version of the database.
1483 	 * @param connection    A connection to the case database.
1484 	 *
1485 	 * @return The new database schema version.
1486 	 *
1487 	 * @throws SQLException     If there is an error completing a database
1488 	 *                          operation.
1489 	 * @throws TskCoreException If there is an error completing a database
1490 	 *                          operation via another SleuthkitCase method.
1491 	 */
updateFromSchema7dot1toSchema7dot2(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1492 	private CaseDbSchemaVersionNumber updateFromSchema7dot1toSchema7dot2(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1493 		if (schemaVersion.getMajor() != 7) {
1494 			return schemaVersion;
1495 		}
1496 
1497 		if (schemaVersion.getMinor() != 1) {
1498 			return schemaVersion;
1499 		}
1500 
1501 		Statement statement = null;
1502 		Statement updstatement = null;
1503 		ResultSet resultSet = null;
1504 		acquireSingleUserCaseWriteLock();
1505 		try {
1506 			//add the data_source_obj_id column to blackboard_artifacts.
1507 			statement = connection.createStatement();
1508 			statement.execute("ALTER TABLE blackboard_artifacts ADD COLUMN data_source_obj_id INTEGER NOT NULL DEFAULT -1");
1509 
1510 			// populate data_source_obj_id for each artifact
1511 			updstatement = connection.createStatement();
1512 			resultSet = connection.executeQuery(statement, "SELECT artifact_id, obj_id FROM blackboard_artifacts"); //NON-NLS
1513 			while (resultSet.next()) {
1514 				long artifact_id = resultSet.getLong("artifact_id");
1515 				long obj_id = resultSet.getLong("obj_id");
1516 				long data_source_obj_id = getDataSourceObjectId(connection, obj_id);
1517 				updstatement.executeUpdate("UPDATE blackboard_artifacts SET data_source_obj_id = " + data_source_obj_id + " "
1518 						+ "WHERE artifact_id = " + artifact_id);
1519 			}
1520 			closeResultSet(resultSet);
1521 			closeStatement(statement);
1522 			closeStatement(updstatement);
1523 
1524 			/*
1525 			 * Add a knownStatus column to the tag_names table.
1526 			 */
1527 			statement = connection.createStatement();
1528 			statement.execute("ALTER TABLE tag_names ADD COLUMN knownStatus INTEGER NOT NULL DEFAULT " + TskData.FileKnown.UNKNOWN.getFileKnownValue());
1529 
1530 			// Create account_types, accounts, and account_relationships  table
1531 			if (this.dbType.equals(DbType.SQLITE)) {
1532 				statement.execute("CREATE TABLE account_types (account_type_id INTEGER PRIMARY KEY, type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)");
1533 				statement.execute("CREATE TABLE accounts (account_id INTEGER PRIMARY KEY, account_type_id INTEGER NOT NULL, account_unique_identifier TEXT NOT NULL,  UNIQUE(account_type_id, account_unique_identifier) , FOREIGN KEY(account_type_id) REFERENCES account_types(account_type_id))");
1534 				statement.execute("CREATE TABLE account_relationships (relationship_id INTEGER PRIMARY KEY, account1_id INTEGER NOT NULL, account2_id INTEGER NOT NULL, relationship_source_obj_id INTEGER NOT NULL,  date_time INTEGER, relationship_type INTEGER NOT NULL, data_source_obj_id INTEGER NOT NULL, UNIQUE(account1_id, account2_id, relationship_source_obj_id), FOREIGN KEY(account1_id) REFERENCES accounts(account_id), FOREIGN KEY(account2_id) REFERENCES accounts(account_id), FOREIGN KEY(relationship_source_obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id))");
1535 			} else {
1536 				statement.execute("CREATE TABLE account_types (account_type_id BIGSERIAL PRIMARY KEY, type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)");
1537 				statement.execute("CREATE TABLE accounts (account_id BIGSERIAL PRIMARY KEY, account_type_id INTEGER NOT NULL, account_unique_identifier TEXT NOT NULL,  UNIQUE(account_type_id, account_unique_identifier) , FOREIGN KEY(account_type_id) REFERENCES account_types(account_type_id))");
1538 				statement.execute("CREATE TABLE account_relationships  (relationship_id BIGSERIAL PRIMARY KEY, account1_id INTEGER NOT NULL, account2_id INTEGER NOT NULL, relationship_source_obj_id INTEGER NOT NULL, date_time BIGINT, relationship_type INTEGER NOT NULL, data_source_obj_id INTEGER NOT NULL, UNIQUE(account1_id, account2_id, relationship_source_obj_id), FOREIGN KEY(account1_id) REFERENCES accounts(account_id), FOREIGN KEY(account2_id) REFERENCES accounts(account_id), FOREIGN KEY(relationship_source_obj_id) REFERENCES tsk_objects(obj_id), FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id))");
1539 			}
1540 
1541 			// Create indexes
1542 			statement.execute("CREATE INDEX artifact_artifact_objID ON blackboard_artifacts(artifact_obj_id)");
1543 			statement.execute("CREATE INDEX relationships_account1  ON account_relationships(account1_id)");
1544 			statement.execute("CREATE INDEX relationships_account2  ON account_relationships(account2_id)");
1545 			statement.execute("CREATE INDEX relationships_relationship_source_obj_id  ON account_relationships(relationship_source_obj_id)");
1546 			statement.execute("CREATE INDEX relationships_date_time  ON account_relationships(date_time)");
1547 			statement.execute("CREATE INDEX relationships_relationship_type  ON account_relationships(relationship_type)");
1548 			statement.execute("CREATE INDEX relationships_data_source_obj_id  ON account_relationships(data_source_obj_id)");
1549 
1550 			return new CaseDbSchemaVersionNumber(7, 2);
1551 		} finally {
1552 			closeResultSet(resultSet);
1553 			closeStatement(statement);
1554 			closeStatement(updstatement);
1555 			releaseSingleUserCaseWriteLock();
1556 		}
1557 	}
1558 
1559 	/**
1560 	 * Updates a schema version 7.2 database to a schema version 8.0 database.
1561 	 *
1562 	 * @param schemaVersion The current schema version of the database.
1563 	 * @param connection    A connection to the case database.
1564 	 *
1565 	 * @return The new database schema version.
1566 	 *
1567 	 * @throws SQLException     If there is an error completing a database
1568 	 *                          operation.
1569 	 * @throws TskCoreException If there is an error completing a database
1570 	 *                          operation via another SleuthkitCase method.
1571 	 */
updateFromSchema7dot2toSchema8dot0(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1572 	private CaseDbSchemaVersionNumber updateFromSchema7dot2toSchema8dot0(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1573 		if (schemaVersion.getMajor() != 7) {
1574 			return schemaVersion;
1575 		}
1576 
1577 		if (schemaVersion.getMinor() != 2) {
1578 			return schemaVersion;
1579 		}
1580 
1581 		Statement updateSchemaStatement = connection.createStatement();
1582 		Statement getExistingReportsStatement = connection.createStatement();
1583 		ResultSet resultSet = null;
1584 		ResultSet existingReports = null;
1585 
1586 		acquireSingleUserCaseWriteLock();
1587 		try {
1588 			// Update the schema to turn report_id into an object id.
1589 
1590 			// Unfortunately, SQLite doesn't support adding a constraint
1591 			// to an existing table so we have to rename the old...
1592 			updateSchemaStatement.execute("ALTER TABLE reports RENAME TO old_reports");
1593 
1594 			// ...create the new...
1595 			updateSchemaStatement.execute("CREATE TABLE reports (obj_id BIGSERIAL PRIMARY KEY, path TEXT NOT NULL, crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id))");
1596 
1597 			// ...add the existing report records back...
1598 			existingReports = getExistingReportsStatement.executeQuery("SELECT * FROM old_reports");
1599 			while (existingReports.next()) {
1600 				String path = existingReports.getString(2);
1601 				long crtime = existingReports.getInt(3);
1602 				String sourceModule = existingReports.getString(4);
1603 				String reportName = existingReports.getString(5);
1604 
1605 				PreparedStatement insertObjectStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_OBJECT, Statement.RETURN_GENERATED_KEYS);
1606 				insertObjectStatement.clearParameters();
1607 				insertObjectStatement.setNull(1, java.sql.Types.BIGINT);
1608 				insertObjectStatement.setLong(2, TskData.ObjectType.REPORT.getObjectType());
1609 				connection.executeUpdate(insertObjectStatement);
1610 				resultSet = insertObjectStatement.getGeneratedKeys();
1611 				if (!resultSet.next()) {
1612 					throw new TskCoreException(String.format("Failed to INSERT report %s (%s) in tsk_objects table", reportName, path));
1613 				}
1614 				long objectId = resultSet.getLong(1); //last_insert_rowid()
1615 
1616 				// INSERT INTO reports (obj_id, path, crtime, src_module_name, display_name) VALUES (?, ?, ?, ?, ?)
1617 				PreparedStatement insertReportStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_REPORT);
1618 				insertReportStatement.clearParameters();
1619 				insertReportStatement.setLong(1, objectId);
1620 				insertReportStatement.setString(2, path);
1621 				insertReportStatement.setLong(3, crtime);
1622 				insertReportStatement.setString(4, sourceModule);
1623 				insertReportStatement.setString(5, reportName);
1624 				connection.executeUpdate(insertReportStatement);
1625 			}
1626 
1627 			// ...and drop the old table.
1628 			updateSchemaStatement.execute("DROP TABLE old_reports");
1629 
1630 			return new CaseDbSchemaVersionNumber(8, 0);
1631 		} finally {
1632 			closeResultSet(resultSet);
1633 			closeResultSet(existingReports);
1634 			closeStatement(updateSchemaStatement);
1635 			closeStatement(getExistingReportsStatement);
1636 			releaseSingleUserCaseWriteLock();
1637 		}
1638 	}
1639 
1640 	/**
1641 	 * Updates a schema version 8.0 database to a schema version 8.1 database.
1642 	 *
1643 	 * @param schemaVersion The current schema version of the database.
1644 	 * @param connection    A connection to the case database.
1645 	 *
1646 	 * @return The new database schema version.
1647 	 *
1648 	 * @throws SQLException     If there is an error completing a database
1649 	 *                          operation.
1650 	 * @throws TskCoreException If there is an error completing a database
1651 	 *                          operation via another SleuthkitCase method.
1652 	 */
updateFromSchema8dot0toSchema8dot1(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1653 	private CaseDbSchemaVersionNumber updateFromSchema8dot0toSchema8dot1(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1654 		if (schemaVersion.getMajor() != 8) {
1655 			return schemaVersion;
1656 		}
1657 
1658 		if (schemaVersion.getMinor() != 0) {
1659 			return schemaVersion;
1660 		}
1661 
1662 		acquireSingleUserCaseWriteLock();
1663 
1664 		try (Statement statement = connection.createStatement();) {
1665 			// create examiners table
1666 			if (this.dbType.equals(DbType.SQLITE)) {
1667 				statement.execute("CREATE TABLE tsk_examiners (examiner_id INTEGER PRIMARY KEY, login_name TEXT NOT NULL, display_name TEXT, UNIQUE(login_name) )");
1668 				statement.execute("ALTER TABLE content_tags ADD COLUMN examiner_id INTEGER REFERENCES tsk_examiners(examiner_id) DEFAULT NULL");
1669 				statement.execute("ALTER TABLE blackboard_artifact_tags ADD COLUMN examiner_id INTEGER REFERENCES tsk_examiners(examiner_id) DEFAULT NULL");
1670 			} else {
1671 				statement.execute("CREATE TABLE tsk_examiners (examiner_id BIGSERIAL PRIMARY KEY, login_name TEXT NOT NULL, display_name TEXT, UNIQUE(login_name))");
1672 				statement.execute("ALTER TABLE content_tags ADD COLUMN examiner_id BIGINT REFERENCES tsk_examiners(examiner_id) DEFAULT NULL");
1673 				statement.execute("ALTER TABLE blackboard_artifact_tags ADD COLUMN examiner_id BIGINT REFERENCES tsk_examiners(examiner_id) DEFAULT NULL");
1674 			}
1675 
1676 			return new CaseDbSchemaVersionNumber(8, 1);
1677 		} finally {
1678 			releaseSingleUserCaseWriteLock();
1679 		}
1680 	}
1681 
1682 	/**
1683 	 * Updates a schema version 8.1 database to a schema version 8.2 database.
1684 	 *
1685 	 * @param schemaVersion The current schema version of the database.
1686 	 * @param connection    A connection to the case database.
1687 	 *
1688 	 * @return The new database schema version.
1689 	 *
1690 	 * @throws SQLException     If there is an error completing a database
1691 	 *                          operation.
1692 	 * @throws TskCoreException If there is an error completing a database
1693 	 *                          operation via another SleuthkitCase method.
1694 	 */
updateFromSchema8dot1toSchema8dot2(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1695 	private CaseDbSchemaVersionNumber updateFromSchema8dot1toSchema8dot2(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1696 		if (schemaVersion.getMajor() != 8) {
1697 			return schemaVersion;
1698 		}
1699 
1700 		if (schemaVersion.getMinor() != 1) {
1701 			return schemaVersion;
1702 		}
1703 
1704 		acquireSingleUserCaseWriteLock();
1705 
1706 		try (Statement statement = connection.createStatement();) {
1707 			statement.execute("ALTER TABLE tsk_image_info ADD COLUMN sha1 TEXT DEFAULT NULL");
1708 			statement.execute("ALTER TABLE tsk_image_info ADD COLUMN sha256 TEXT DEFAULT NULL");
1709 
1710 			statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_details TEXT");
1711 
1712 			/*
1713 			 * Add new tsk_db_extended_info table with TSK version, creation
1714 			 * time schema and schema version numbers as the initial data. The
1715 			 * creation time schema version is set to 0, 0 to indicate that it
1716 			 * is not known.
1717 			 */
1718 			statement.execute("CREATE TABLE tsk_db_info_extended (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
1719 			ResultSet result = statement.executeQuery("SELECT tsk_ver FROM tsk_db_info");
1720 			result.next();
1721 			statement.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('" + TSK_VERSION_KEY + "', '" + result.getLong("tsk_ver") + "')");
1722 			statement.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('" + SCHEMA_MAJOR_VERSION_KEY + "', '8')");
1723 			statement.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('" + SCHEMA_MINOR_VERSION_KEY + "', '2')");
1724 			statement.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('" + CREATION_SCHEMA_MAJOR_VERSION_KEY + "', '0')");
1725 			statement.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('" + CREATION_SCHEMA_MINOR_VERSION_KEY + "', '0')");
1726 
1727 			String primaryKeyType;
1728 			switch (getDatabaseType()) {
1729 				case POSTGRESQL:
1730 					primaryKeyType = "BIGSERIAL";
1731 					break;
1732 				case SQLITE:
1733 					primaryKeyType = "INTEGER";
1734 					break;
1735 				default:
1736 					throw new TskCoreException("Unsupported data base type: " + getDatabaseType().toString());
1737 			}
1738 
1739 			//create and initialize tsk_event_types tables
1740 			statement.execute("CREATE TABLE tsk_event_types ("
1741 					+ " event_type_id " + primaryKeyType + " PRIMARY KEY, "
1742 					+ " display_name TEXT UNIQUE NOT NULL, "
1743 					+ " super_type_id INTEGER REFERENCES tsk_event_types(event_type_id) )");
1744 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1745 					+ " values( 0, 'Event Types', null)");
1746 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1747 					+ " values(1, 'File System', 0)");
1748 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1749 					+ " values(2, 'Web Activity', 0)");
1750 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1751 					+ " values(3, 'Misc Types', 0)");
1752 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1753 					+ " values(4, 'Modified', 1)");
1754 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1755 					+ " values(5, 'Accessed', 1)");
1756 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1757 					+ " values(6, 'Created', 1)");
1758 			statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1759 					+ " values(7, 'Changed', 1)");
1760 
1761 			//create tsk_events tables
1762 			statement.execute("CREATE TABLE tsk_event_descriptions ("
1763 					+ " event_description_id " + primaryKeyType + " PRIMARY KEY, "
1764 					+ " full_description TEXT NOT NULL, "
1765 					+ " med_description TEXT, "
1766 					+ " short_description TEXT,"
1767 					+ " data_source_obj_id BIGINT NOT NULL, "
1768 					+ " file_obj_id BIGINT NOT NULL, "
1769 					+ " artifact_id BIGINT, "
1770 					+ " hash_hit INTEGER NOT NULL, " //boolean
1771 					+ " tagged INTEGER NOT NULL, " //boolean
1772 					+ " FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id), "
1773 					+ " FOREIGN KEY(file_obj_id) REFERENCES tsk_files(obj_id), "
1774 					+ " FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id))"
1775 			);
1776 
1777 			statement.execute("CREATE TABLE tsk_events ( "
1778 					+ " event_id " + primaryKeyType + " PRIMARY KEY, "
1779 					+ " event_type_id BIGINT NOT NULL REFERENCES tsk_event_types(event_type_id) ,"
1780 					+ " event_description_id BIGINT NOT NULL REFERENCES tsk_event_descriptions(event_description_id) ,"
1781 					+ " time INTEGER NOT NULL) "
1782 			);
1783 
1784 			//create tsk_events indices
1785 			statement.execute("CREATE INDEX events_time ON tsk_events(time)");
1786 			statement.execute("CREATE INDEX events_type ON tsk_events(event_type_id)");
1787 			statement.execute("CREATE INDEX events_data_source_obj_id  ON tsk_event_descriptions(data_source_obj_id) ");
1788 			statement.execute("CREATE INDEX events_file_obj_id  ON tsk_event_descriptions(file_obj_id ");
1789 			statement.execute("CREATE INDEX events_artifact_id  ON tsk_event_descriptions(artifact_id) ");
1790 			statement.execute("CREATE INDEX events_sub_type_time ON tsk_events(event_type_id,  time) ");
1791 			statement.execute("CREATE INDEX events_time  ON tsk_events(time ");
1792 			return new CaseDbSchemaVersionNumber(8, 2);
1793 
1794 		} finally {
1795 			releaseSingleUserCaseWriteLock();
1796 		}
1797 	}
1798 
1799 	/**
1800 	 * Updates a schema version 8.2 database to a schema version 8.3 database.
1801 	 *
1802 	 * @param schemaVersion The current schema version of the database.
1803 	 * @param connection    A connection to the case database.
1804 	 *
1805 	 * @return The new database schema version.
1806 	 *
1807 	 * @throws SQLException     If there is an error completing a database
1808 	 *                          operation.
1809 	 * @throws TskCoreException If there is an error completing a database
1810 	 *                          operation via another SleuthkitCase method.
1811 	 */
updateFromSchema8dot2toSchema8dot3(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection)1812 	private CaseDbSchemaVersionNumber updateFromSchema8dot2toSchema8dot3(CaseDbSchemaVersionNumber schemaVersion, CaseDbConnection connection) throws SQLException, TskCoreException {
1813 		if (schemaVersion.getMajor() != 8) {
1814 			return schemaVersion;
1815 		}
1816 
1817 		if (schemaVersion.getMinor() != 2) {
1818 			return schemaVersion;
1819 		}
1820 
1821 		acquireSingleUserCaseWriteLock();
1822 
1823 		ResultSet resultSet = null;
1824 
1825 		try (Statement statement = connection.createStatement();) {
1826 
1827 			// Add the uniqueness constraint to the tsk_event and tsk_event_description tables.
1828 			// Unfortunately, SQLite doesn't support adding a constraint
1829 			// to an existing table so we have to rename the old...
1830 			String primaryKeyType;
1831 			switch (getDatabaseType()) {
1832 				case POSTGRESQL:
1833 					primaryKeyType = "BIGSERIAL";
1834 					break;
1835 				case SQLITE:
1836 					primaryKeyType = "INTEGER";
1837 					break;
1838 				default:
1839 					throw new TskCoreException("Unsupported data base type: " + getDatabaseType().toString());
1840 			}
1841 
1842 			//create and initialize tsk_event_types tables which may or may not exist
1843 			statement.execute("CREATE TABLE IF NOT EXISTS tsk_event_types ("
1844 					+ " event_type_id " + primaryKeyType + " PRIMARY KEY, "
1845 					+ " display_name TEXT UNIQUE NOT NULL, "
1846 					+ " super_type_id INTEGER REFERENCES tsk_event_types(event_type_id) )");
1847 
1848 			resultSet = statement.executeQuery("SELECT * from tsk_event_types");
1849 
1850 			// If there is something in resultSet then the table must have previously
1851 			// existing therefore there is not need to populate
1852 			if (!resultSet.next()) {
1853 
1854 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1855 						+ " values( 0, 'Event Types', null)");
1856 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1857 						+ " values(1, 'File System', 0)");
1858 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1859 						+ " values(2, 'Web Activity', 0)");
1860 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1861 						+ " values(3, 'Misc Types', 0)");
1862 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1863 						+ " values(4, 'Modified', 1)");
1864 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1865 						+ " values(5, 'Accessed', 1)");
1866 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1867 						+ " values(6, 'Created', 1)");
1868 				statement.execute("insert into tsk_event_types(event_type_id, display_name, super_type_id)"
1869 						+ " values(7, 'Changed', 1)");
1870 			}
1871 
1872 			// Delete the old table that may have been created with the upgrade
1873 			// from 8.1 to 8.2.
1874 			statement.execute("DROP TABLE IF EXISTS tsk_events");
1875 
1876 			// Delete the old table that may have been created with the upgrade
1877 			// from 8.1 to 8.2
1878 			statement.execute("DROP TABLE IF EXISTS tsk_event_descriptions");
1879 
1880 			//create new tsk_event_description table
1881 			statement.execute("CREATE TABLE tsk_event_descriptions ("
1882 					+ " event_description_id " + primaryKeyType + " PRIMARY KEY, "
1883 					+ " full_description TEXT NOT NULL, "
1884 					+ " med_description TEXT, "
1885 					+ " short_description TEXT,"
1886 					+ " data_source_obj_id BIGINT NOT NULL, "
1887 					+ " file_obj_id BIGINT NOT NULL, "
1888 					+ " artifact_id BIGINT, "
1889 					+ " hash_hit INTEGER NOT NULL, " //boolean
1890 					+ " tagged INTEGER NOT NULL, " //boolean
1891 					+ " UNIQUE(full_description, file_obj_id, artifact_id), "
1892 					+ " FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id), "
1893 					+ " FOREIGN KEY(file_obj_id) REFERENCES tsk_files(obj_id), "
1894 					+ " FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id))"
1895 			);
1896 
1897 			// create a new table
1898 			statement.execute("CREATE TABLE tsk_events ( "
1899 					+ " event_id " + primaryKeyType + " PRIMARY KEY, "
1900 					+ " event_type_id BIGINT NOT NULL REFERENCES tsk_event_types(event_type_id) ,"
1901 					+ " event_description_id BIGINT NOT NULL REFERENCES tsk_event_descriptions(event_description_id) ,"
1902 					+ " time INTEGER NOT NULL, "
1903 					+ " UNIQUE (event_type_id, event_description_id, time))"
1904 			);
1905 
1906 			// Fix mistakenly set names in tsk_db_info_extended
1907 			statement.execute("UPDATE tsk_db_info_extended SET name = 'CREATION_SCHEMA_MAJOR_VERION' WHERE name = 'CREATED_SCHEMA_MAJOR_VERSION'");
1908 			statement.execute("UPDATE tsk_db_info_extended SET name = 'CREATION_SCHEMA_MINOR_VERION' WHERE name = 'CREATED_SCHEMA_MINOR_VERSION'");
1909 
1910 			return new CaseDbSchemaVersionNumber(8, 3);
1911 		} finally {
1912 			closeResultSet(resultSet);
1913 			releaseSingleUserCaseWriteLock();
1914 		}
1915 	}
1916 
1917 	/**
1918 	 * Extract the extension from a file name.
1919 	 *
1920 	 * @param fileName the file name to extract the extension from.
1921 	 *
1922 	 * @return The extension extracted from fileName. Will not be null.
1923 	 */
extractExtension(final String fileName)1924 	static String extractExtension(final String fileName) {
1925 		String ext;
1926 		int i = fileName.lastIndexOf(".");
1927 		// > 0 because we assume it's not an extension if period is the first character
1928 		if ((i > 0) && ((i + 1) < fileName.length())) {
1929 			ext = fileName.substring(i + 1);
1930 		} else {
1931 			return "";
1932 		}
1933 		// we added this at one point to deal with files that had crazy names based on URLs
1934 		// it's too hard though to clean those up and not mess up basic extensions though.
1935 		// We need to add '-' to the below if we use it again
1936 		//		String[] findNonAlphanumeric = ext.split("[^a-zA-Z0-9_]");
1937 		//		if (findNonAlphanumeric.length > 1) {
1938 		//			ext = findNonAlphanumeric[0];
1939 		//		}
1940 		return ext.toLowerCase();
1941 	}
1942 
1943 	/**
1944 	 * Returns case database schema version number. As of TSK 4.5.0 db schema
1945 	 * versions are two part Major.minor. This method only returns the major
1946 	 * part. Use getDBSchemaVersion() for the complete version.
1947 	 *
1948 	 * @return The schema version number as an integer.
1949 	 *
1950 	 * @deprecated since 4.5.0 Use getDBSchemaVersion() instead for more
1951 	 * complete version info.
1952 	 */
1953 	@Deprecated
getSchemaVersion()1954 	public int getSchemaVersion() {
1955 		return getDBSchemaVersion().getMajor();
1956 	}
1957 
1958 	/**
1959 	 * Gets the database schema version in use.
1960 	 *
1961 	 * @return the database schema version in use.
1962 	 */
getDBSchemaVersion()1963 	public VersionNumber getDBSchemaVersion() {
1964 		return CURRENT_DB_SCHEMA_VERSION;
1965 	}
1966 
1967 	/**
1968 	 * Gets the creation version of the database schema.
1969 	 *
1970 	 * @return the creation version for the database schema, the creation
1971 	 *         version will be 0.0 for databases created prior to 8.2
1972 	 */
getDBSchemaCreationVersion()1973 	public CaseDbSchemaVersionNumber getDBSchemaCreationVersion() {
1974 		return caseDBSchemaCreationVersion;
1975 	}
1976 
1977 	/**
1978 	 * Returns the type of database in use.
1979 	 *
1980 	 * @return database type
1981 	 */
getDatabaseType()1982 	public DbType getDatabaseType() {
1983 		return this.dbType;
1984 	}
1985 
1986 	/**
1987 	 * Returns the path of a backup copy of the database made when a schema
1988 	 * version upgrade has occurred.
1989 	 *
1990 	 * @return The path of the backup file or null if no backup was made.
1991 	 */
getBackupDatabasePath()1992 	public String getBackupDatabasePath() {
1993 		return dbBackupPath;
1994 	}
1995 
1996 	/**
1997 	 * Create a new transaction on the case database. The transaction object
1998 	 * that is returned can be passed to methods that take a CaseDbTransaction.
1999 	 * The caller is responsible for calling either commit() or rollback() on
2000 	 * the transaction object.
2001 	 *
2002 	 * @return A CaseDbTransaction object.
2003 	 *
2004 	 * @throws TskCoreException
2005 	 */
beginTransaction()2006 	public CaseDbTransaction beginTransaction() throws TskCoreException {
2007 		return new CaseDbTransaction(this, connections.getConnection());
2008 	}
2009 
2010 	/**
2011 	 * Gets the case database name.
2012 	 *
2013 	 * @return The case database name.
2014 	 */
getDatabaseName()2015 	public String getDatabaseName() {
2016 		return databaseName;
2017 	}
2018 
2019 	/**
2020 	 * Get the full path to the case directory. For a SQLite case database, this
2021 	 * is the same as the database directory path.
2022 	 *
2023 	 * @return Case directory path.
2024 	 */
getDbDirPath()2025 	public String getDbDirPath() {
2026 		return caseDirPath;
2027 	}
2028 
2029 	/**
2030 	 * Acquires a write lock, but only if this is a single-user case. Always
2031 	 * call this method in a try block with a call to the lock release method in
2032 	 * an associated finally block.
2033 	 */
acquireSingleUserCaseWriteLock()2034 	public void acquireSingleUserCaseWriteLock() {
2035 		if (dbType == DbType.SQLITE) {
2036 			rwLock.writeLock().lock();
2037 		}
2038 	}
2039 
2040 	/**
2041 	 * Releases a write lock, but only if this is a single-user case. This
2042 	 * method should always be called in the finally block of a try block in
2043 	 * which the lock was acquired.
2044 	 */
releaseSingleUserCaseWriteLock()2045 	public void releaseSingleUserCaseWriteLock() {
2046 		if (dbType == DbType.SQLITE) {
2047 			rwLock.writeLock().unlock();
2048 		}
2049 	}
2050 
2051 	/**
2052 	 * Acquires a read lock, but only if this is a single-user case. Call this
2053 	 * method in a try block with a call to the lock release method in an
2054 	 * associated finally block.
2055 	 */
acquireSingleUserCaseReadLock()2056 	public void acquireSingleUserCaseReadLock() {
2057 		if (dbType == DbType.SQLITE) {
2058 			rwLock.readLock().lock();
2059 		}
2060 	}
2061 
2062 	/**
2063 	 * Releases a read lock, but only if this is a single-user case. This method
2064 	 * should always be called in the finally block of a try block in which the
2065 	 * lock was acquired.
2066 	 */
releaseSingleUserCaseReadLock()2067 	public void releaseSingleUserCaseReadLock() {
2068 		if (dbType == DbType.SQLITE) {
2069 			rwLock.readLock().unlock();
2070 		}
2071 	}
2072 
2073 	/**
2074 	 * Open an existing case database.
2075 	 *
2076 	 * @param dbPath Path to SQLite case database.
2077 	 *
2078 	 * @return Case database object.
2079 	 *
2080 	 * @throws org.sleuthkit.datamodel.TskCoreException
2081 	 */
openCase(String dbPath)2082 	public static SleuthkitCase openCase(String dbPath) throws TskCoreException {
2083 		try {
2084 			final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(dbPath);
2085 			return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE);
2086 		} catch (TskUnsupportedSchemaVersionException ex) {
2087 			//don't wrap in new TskCoreException
2088 			throw ex;
2089 		} catch (Exception ex) {
2090 			throw new TskCoreException("Failed to open case database at " + dbPath, ex);
2091 		}
2092 	}
2093 
2094 	/**
2095 	 * Open an existing multi-user case database.
2096 	 *
2097 	 * @param databaseName The name of the database.
2098 	 * @param info         Connection information for the the database.
2099 	 * @param caseDir      The folder where the case metadata fils is stored.
2100 	 *
2101 	 * @return A case database object.
2102 	 *
2103 	 * @throws TskCoreException If there is a problem opening the database.
2104 	 */
openCase(String databaseName, CaseDbConnectionInfo info, String caseDir)2105 	public static SleuthkitCase openCase(String databaseName, CaseDbConnectionInfo info, String caseDir) throws TskCoreException {
2106 		try {
2107 			/*
2108 			 * The flow of this method involves trying to open case and if
2109 			 * successful, return that case. If unsuccessful, an exception is
2110 			 * thrown. We catch any exceptions, and use tryConnect() to attempt
2111 			 * to obtain further information about the error. If tryConnect() is
2112 			 * unable to successfully connect, tryConnect() will throw a
2113 			 * TskCoreException with a message containing user-level error
2114 			 * reporting. If tryConnect() is able to connect, flow continues and
2115 			 * we rethrow the original exception obtained from trying to create
2116 			 * the case. In this way, we obtain more detailed information if we
2117 			 * are able, but do not lose any information if unable.
2118 			 */
2119 			final SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.openCaseDb(databaseName, info);
2120 			return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()), databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDir, info.getDbType());
2121 		} catch (PropertyVetoException exp) {
2122 			// In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here.
2123 			throw new TskCoreException(exp.getMessage(), exp);
2124 		} catch (TskUnsupportedSchemaVersionException ex) {
2125 			//don't wrap in new TskCoreException
2126 			throw ex;
2127 		} catch (Exception exp) {
2128 			tryConnect(info); // attempt to connect, throw with user-friendly message if unable
2129 			throw new TskCoreException(exp.getMessage(), exp); // throw with generic message if tryConnect() was successful
2130 		}
2131 	}
2132 
2133 	/**
2134 	 * Creates a new SQLite case database.
2135 	 *
2136 	 * @param dbPath Path to where SQlite case database should be created.
2137 	 *
2138 	 * @return A case database object.
2139 	 *
2140 	 * @throws org.sleuthkit.datamodel.TskCoreException
2141 	 */
newCase(String dbPath)2142 	public static SleuthkitCase newCase(String dbPath) throws TskCoreException {
2143 		try {
2144 			SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.newCaseDb(dbPath);
2145 			return new SleuthkitCase(dbPath, caseHandle, DbType.SQLITE);
2146 		} catch (Exception ex) {
2147 			throw new TskCoreException("Failed to create case database at " + dbPath, ex);
2148 		}
2149 	}
2150 
2151 	/**
2152 	 * Creates a new PostgreSQL case database.
2153 	 *
2154 	 * @param caseName    The name of the case. It will be used to create a case
2155 	 *                    database name that can be safely used in SQL commands
2156 	 *                    and will not be subject to name collisions on the case
2157 	 *                    database server. Use getDatabaseName to get the
2158 	 *                    created name.
2159 	 * @param info        The information to connect to the database.
2160 	 * @param caseDirPath The case directory path.
2161 	 *
2162 	 * @return A case database object.
2163 	 *
2164 	 * @throws org.sleuthkit.datamodel.TskCoreException
2165 	 */
newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath)2166 	public static SleuthkitCase newCase(String caseName, CaseDbConnectionInfo info, String caseDirPath) throws TskCoreException {
2167 		String databaseName = createCaseDataBaseName(caseName);
2168 		try {
2169 			/**
2170 			 * The flow of this method involves trying to create a new case and
2171 			 * if successful, return that case. If unsuccessful, an exception is
2172 			 * thrown. We catch any exceptions, and use tryConnect() to attempt
2173 			 * to obtain further information about the error. If tryConnect() is
2174 			 * unable to successfully connect, tryConnect() will throw a
2175 			 * TskCoreException with a message containing user-level error
2176 			 * reporting. If tryConnect() is able to connect, flow continues and
2177 			 * we rethrow the original exception obtained from trying to create
2178 			 * the case. In this way, we obtain more detailed information if we
2179 			 * are able, but do not lose any information if unable.
2180 			 */
2181 			SleuthkitJNI.CaseDbHandle caseHandle = SleuthkitJNI.newCaseDb(databaseName, info);
2182 			return new SleuthkitCase(info.getHost(), Integer.parseInt(info.getPort()),
2183 					databaseName, info.getUserName(), info.getPassword(), caseHandle, caseDirPath, info.getDbType());
2184 		} catch (PropertyVetoException exp) {
2185 			// In this case, the JDBC driver doesn't support PostgreSQL. Use the generic message here.
2186 			throw new TskCoreException(exp.getMessage(), exp);
2187 		} catch (Exception exp) {
2188 			tryConnect(info); // attempt to connect, throw with user-friendly message if unable
2189 			throw new TskCoreException(exp.getMessage(), exp); // throw with generic message if tryConnect() was successful
2190 		}
2191 	}
2192 
2193 	/**
2194 	 * Transforms a candidate PostgreSQL case database name into one that can be
2195 	 * safely used in SQL commands and will not be subject to name collisions on
2196 	 * the case database server.
2197 	 *
2198 	 * @param candidateDbName A candidate case database name.
2199 	 *
2200 	 * @return A case database name.
2201 	 */
createCaseDataBaseName(String candidateDbName)2202 	private static String createCaseDataBaseName(String candidateDbName) {
2203 		String dbName;
2204 		if (!candidateDbName.isEmpty()) {
2205 			/*
2206 			 * Replace all non-ASCII characters.
2207 			 */
2208 			dbName = candidateDbName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
2209 
2210 			/*
2211 			 * Replace all control characters.
2212 			 */
2213 			dbName = dbName.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
2214 
2215 			/*
2216 			 * Replace /, \, :, ?, space, ' ".
2217 			 */
2218 			dbName = dbName.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
2219 
2220 			/*
2221 			 * Make it all lowercase.
2222 			 */
2223 			dbName = dbName.toLowerCase();
2224 
2225 			/*
2226 			 * Must start with letter or underscore. If not, prepend an
2227 			 * underscore.
2228 			 */
2229 			if ((dbName.length() > 0 && !(Character.isLetter(dbName.codePointAt(0))) && !(dbName.codePointAt(0) == '_'))) {
2230 				dbName = "_" + dbName;
2231 			}
2232 
2233 			/*
2234 			 * Truncate to 63 - 16 = 47 chars to accomodate a timestamp for
2235 			 * uniqueness.
2236 			 */
2237 			if (dbName.length() > MAX_DB_NAME_LEN_BEFORE_TIMESTAMP) {
2238 				dbName = dbName.substring(0, MAX_DB_NAME_LEN_BEFORE_TIMESTAMP);
2239 			}
2240 
2241 		} else {
2242 			/*
2243 			 * Must start with letter or underscore.
2244 			 */
2245 			dbName = "_";
2246 		}
2247 		/*
2248 		 * Add the time stmap.
2249 		 */
2250 		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
2251 		Date date = new Date();
2252 		dbName = dbName + "_" + dateFormat.format(date);
2253 
2254 		return dbName;
2255 	}
2256 
2257 	/**
2258 	 * Returns the Examiner object for currently logged in user
2259 	 *
2260 	 * @return A Examiner object.
2261 	 *
2262 	 * @throws org.sleuthkit.datamodel.TskCoreException
2263 	 */
getCurrentExaminer()2264 	public Examiner getCurrentExaminer() throws TskCoreException {
2265 
2266 		// return cached value if there's one
2267 		if (cachedCurrentExaminer != null) {
2268 			return cachedCurrentExaminer;
2269 		}
2270 		String loginName = System.getProperty("user.name");
2271 		if (loginName == null || loginName.isEmpty()) {
2272 			throw new TskCoreException("Failed to determine logged in user name.");
2273 		}
2274 
2275 		CaseDbConnection connection = connections.getConnection();
2276 		acquireSingleUserCaseReadLock();
2277 		ResultSet resultSet = null;
2278 		try {
2279 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_EXAMINER_BY_LOGIN_NAME);
2280 			statement.clearParameters();
2281 			statement.setString(1, loginName);
2282 			resultSet = connection.executeQuery(statement);
2283 			if (resultSet.next()) {
2284 				cachedCurrentExaminer = new Examiner(resultSet.getLong("examiner_id"), resultSet.getString("login_name"), resultSet.getString("display_name"));
2285 				return cachedCurrentExaminer;
2286 			} else {
2287 				throw new TskCoreException("Error getting examaminer for name = " + loginName);
2288 			}
2289 
2290 		} catch (SQLException ex) {
2291 			throw new TskCoreException("Error getting examaminer for name = " + loginName, ex);
2292 		} finally {
2293 			closeResultSet(resultSet);
2294 			connection.close();
2295 			releaseSingleUserCaseReadLock();
2296 		}
2297 
2298 	}
2299 
2300 	/**
2301 	 * Returns the Examiner object for given id
2302 	 *
2303 	 * @param id
2304 	 *
2305 	 * @return Examiner object
2306 	 *
2307 	 * @throws TskCoreException
2308 	 */
getExaminerById(long id)2309 	Examiner getExaminerById(long id) throws TskCoreException {
2310 
2311 		CaseDbConnection connection = connections.getConnection();
2312 		acquireSingleUserCaseReadLock();
2313 		ResultSet resultSet = null;
2314 		try {
2315 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_EXAMINER_BY_ID);
2316 			statement.clearParameters();
2317 			statement.setLong(1, id);
2318 			resultSet = connection.executeQuery(statement);
2319 			if (resultSet.next()) {
2320 				return new Examiner(resultSet.getLong("examiner_id"), resultSet.getString("login_name"), resultSet.getString("full_name"));
2321 			} else {
2322 				throw new TskCoreException("Error getting examaminer for id = " + id);
2323 			}
2324 		} catch (SQLException ex) {
2325 			throw new TskCoreException("Error getting examaminer for id = " + id, ex);
2326 		} finally {
2327 			closeResultSet(resultSet);
2328 			connection.close();
2329 			releaseSingleUserCaseReadLock();
2330 		}
2331 	}
2332 
2333 	/**
2334 	 * Starts the multi-step process of adding an image data source to the case
2335 	 * by creating an object that can be used to control the process and get
2336 	 * progress messages from it.
2337 	 *
2338 	 * @param timeZone        The time zone of the image.
2339 	 * @param addUnallocSpace Set to true to create virtual files for
2340 	 *                        unallocated space in the image.
2341 	 * @param noFatFsOrphans  Set to true to skip processing orphan files of FAT
2342 	 *                        file systems.
2343 	 * @param imageCopyPath   Path to which a copy of the image should be
2344 	 *                        written. Use the empty string to disable image
2345 	 *                        writing.
2346 	 *
2347 	 * @return An object that encapsulates control of adding an image via the
2348 	 *         SleuthKit native code layer.
2349 	 */
makeAddImageProcess(String timeZone, boolean addUnallocSpace, boolean noFatFsOrphans, String imageCopyPath)2350 	public AddImageProcess makeAddImageProcess(String timeZone, boolean addUnallocSpace, boolean noFatFsOrphans, String imageCopyPath) {
2351 		return this.caseHandle.initAddImageProcess(timeZone, addUnallocSpace, noFatFsOrphans, imageCopyPath, this);
2352 	}
2353 
2354 	/**
2355 	 * Get the list of root objects (data sources) from the case database, e.g.,
2356 	 * image files, logical (local) files, virtual directories.
2357 	 *
2358 	 * @return List of content objects representing root objects.
2359 	 *
2360 	 * @throws TskCoreException
2361 	 */
getRootObjects()2362 	public List<Content> getRootObjects() throws TskCoreException {
2363 		CaseDbConnection connection = connections.getConnection();
2364 		acquireSingleUserCaseReadLock();
2365 		Statement s = null;
2366 		ResultSet rs = null;
2367 		try {
2368 			s = connection.createStatement();
2369 			rs = connection.executeQuery(s, "SELECT obj_id, type FROM tsk_objects " //NON-NLS
2370 					+ "WHERE par_obj_id IS NULL"); //NON-NLS
2371 			Collection<ObjectInfo> infos = new ArrayList<ObjectInfo>();
2372 			while (rs.next()) {
2373 				infos.add(new ObjectInfo(rs.getLong("obj_id"), ObjectType.valueOf(rs.getShort("type")))); //NON-NLS
2374 			}
2375 
2376 			List<Content> rootObjs = new ArrayList<Content>();
2377 			for (ObjectInfo i : infos) {
2378 				if (null != i.type) {
2379 					switch (i.type) {
2380 						case IMG:
2381 							rootObjs.add(getImageById(i.id));
2382 							break;
2383 						case ABSTRACTFILE:
2384 							// Check if virtual dir for local files.
2385 							AbstractFile af = getAbstractFileById(i.id);
2386 							if (af instanceof VirtualDirectory) {
2387 								rootObjs.add(af);
2388 							} else {
2389 								throw new TskCoreException("Parentless object has wrong type to be a root (ABSTRACTFILE, but not VIRTUAL_DIRECTORY: " + i.type);
2390 							}
2391 							break;
2392 						case REPORT:
2393 							break;
2394 						default:
2395 							throw new TskCoreException("Parentless object has wrong type to be a root: " + i.type);
2396 					}
2397 				}
2398 			}
2399 			return rootObjs;
2400 		} catch (SQLException ex) {
2401 			throw new TskCoreException("Error getting root objects", ex);
2402 		} finally {
2403 			closeResultSet(rs);
2404 			closeStatement(s);
2405 			connection.close();
2406 			releaseSingleUserCaseReadLock();
2407 		}
2408 	}
2409 
2410 	/**
2411 	 * Gets the the datasource obj ids for the given device_id
2412 	 *
2413 	 * @param deviceId device_id
2414 	 *
2415 	 * @return A list of the data source object_id for the given device_id for
2416 	 *         the case.
2417 	 *
2418 	 * @throws TskCoreException if there is a problem getting the data source
2419 	 *                          obj ids.
2420 	 */
getDataSourceObjIds(String deviceId)2421 	List<Long> getDataSourceObjIds(String deviceId) throws TskCoreException {
2422 
2423 		// check cached map first
2424 		synchronized (deviceIdToDatasourceObjIdMap) {
2425 			if (deviceIdToDatasourceObjIdMap.containsKey(deviceId)) {
2426 				return new ArrayList<Long>(deviceIdToDatasourceObjIdMap.get(deviceId));
2427 			}
2428 
2429 			CaseDbConnection connection = connections.getConnection();
2430 			acquireSingleUserCaseReadLock();
2431 			Statement s = null;
2432 			ResultSet rs = null;
2433 			try {
2434 				s = connection.createStatement();
2435 				rs = connection.executeQuery(s, "SELECT obj_id FROM data_source_info WHERE device_id = '" + deviceId + "'"); //NON-NLS
2436 				List<Long> dataSourceObjIds = new ArrayList<Long>();
2437 				while (rs.next()) {
2438 					dataSourceObjIds.add(rs.getLong("obj_id"));
2439 
2440 					// Add to map of deviceID to data_source_obj_id.
2441 					long ds_obj_id = rs.getLong("obj_id");
2442 					if (deviceIdToDatasourceObjIdMap.containsKey(deviceId)) {
2443 						deviceIdToDatasourceObjIdMap.get(deviceId).add(ds_obj_id);
2444 					} else {
2445 						deviceIdToDatasourceObjIdMap.put(deviceId, new HashSet<Long>(Arrays.asList(ds_obj_id)));
2446 					}
2447 				}
2448 				return dataSourceObjIds;
2449 			} catch (SQLException ex) {
2450 				throw new TskCoreException("Error getting data sources", ex);
2451 			} finally {
2452 				closeResultSet(rs);
2453 				closeStatement(s);
2454 				connection.close();
2455 				releaseSingleUserCaseReadLock();
2456 			}
2457 		}
2458 	}
2459 
2460 	/**
2461 	 * Gets the data sources for the case. For each data source, if it is an
2462 	 * image, an Image will be instantiated. Otherwise, a LocalFilesDataSource
2463 	 * will be instantiated.
2464 	 *
2465 	 * NOTE: The DataSource interface is an emerging feature and at present is
2466 	 * only useful for obtaining the object id and the device id, an
2467 	 * ASCII-printable identifier for the device associated with the data source
2468 	 * that is intended to be unique across multiple cases (e.g., a UUID). In
2469 	 * the future, this method will be a replacement for the getRootObjects
2470 	 * method.
2471 	 *
2472 	 * @return A list of the data sources for the case.
2473 	 *
2474 	 * @throws TskCoreException if there is a problem getting the data sources.
2475 	 */
getDataSources()2476 	public List<DataSource> getDataSources() throws TskCoreException {
2477 		CaseDbConnection connection = connections.getConnection();
2478 		acquireSingleUserCaseReadLock();
2479 		Statement statement = null;
2480 		ResultSet resultSet = null;
2481 		Statement statement2 = null;
2482 		ResultSet resultSet2 = null;
2483 		try {
2484 			statement = connection.createStatement();
2485 			statement2 = connection.createStatement();
2486 			resultSet = connection.executeQuery(statement,
2487 					"SELECT ds.obj_id, ds.device_id, ds.time_zone, img.type, img.ssize, img.size, img.md5, img.sha1, img.sha256, img.display_name "
2488 					+ "FROM data_source_info AS ds "
2489 					+ "LEFT JOIN tsk_image_info AS img "
2490 					+ "ON ds.obj_id = img.obj_id"); //NON-NLS
2491 
2492 			List<DataSource> dataSourceList = new ArrayList<DataSource>();
2493 			Map<Long, List<String>> imagePathsMap = getImagePaths();
2494 
2495 			while (resultSet.next()) {
2496 				DataSource dataSource;
2497 				Long objectId = resultSet.getLong("obj_id");
2498 				String deviceId = resultSet.getString("device_id");
2499 				String timezone = resultSet.getString("time_zone");
2500 				String type = resultSet.getString("type");
2501 
2502 				if (type == null) {
2503 					/*
2504 					 * No data found in 'tsk_image_info', so we build a
2505 					 * LocalFilesDataSource.
2506 					 */
2507 
2508 					resultSet2 = connection.executeQuery(statement2, "SELECT name FROM tsk_files WHERE tsk_files.obj_id = " + objectId); //NON-NLS
2509 					String dsName = (resultSet2.next()) ? resultSet2.getString("name") : "";
2510 					resultSet2.close();
2511 
2512 					TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
2513 					TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
2514 					TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
2515 					final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
2516 							| TSK_FS_META_FLAG_ENUM.USED.getValue());
2517 					String parentPath = "/"; //NON-NLS
2518 					dataSource = new LocalFilesDataSource(this, objectId, objectId, deviceId, dsName, dirType, metaType, dirFlag, metaFlags, timezone, null, FileKnown.UNKNOWN, parentPath);
2519 				} else {
2520 					/*
2521 					 * Data found in 'tsk_image_info', so we build an Image.
2522 					 */
2523 					Long ssize = resultSet.getLong("ssize");
2524 					Long size = resultSet.getLong("size");
2525 					String md5 = resultSet.getString("md5");
2526 					String sha1 = resultSet.getString("sha1");
2527 					String sha256 = resultSet.getString("sha256");
2528 					String name = resultSet.getString("display_name");
2529 
2530 					List<String> imagePaths = imagePathsMap.get(objectId);
2531 					if (name == null) {
2532 						if (imagePaths.size() > 0) {
2533 							String path = imagePaths.get(0);
2534 							name = (new java.io.File(path)).getName();
2535 						} else {
2536 							name = "";
2537 						}
2538 					}
2539 
2540 					dataSource = new Image(this, objectId, Long.valueOf(type), deviceId, ssize, name,
2541 							imagePaths.toArray(new String[imagePaths.size()]), timezone, md5, sha1, sha256, size);
2542 				}
2543 
2544 				dataSourceList.add(dataSource);
2545 			}
2546 
2547 			return dataSourceList;
2548 
2549 		} catch (SQLException ex) {
2550 			throw new TskCoreException("Error getting data sources", ex);
2551 		} finally {
2552 			closeResultSet(resultSet);
2553 			closeStatement(statement);
2554 			closeResultSet(resultSet2);
2555 			closeStatement(statement2);
2556 			connection.close();
2557 			releaseSingleUserCaseReadLock();
2558 		}
2559 	}
2560 
2561 	/**
2562 	 * Gets a specific data source for the case. If it is an image, an Image
2563 	 * will be instantiated. Otherwise, a LocalFilesDataSource will be
2564 	 * instantiated.
2565 	 *
2566 	 * NOTE: The DataSource class is an emerging feature and at present is only
2567 	 * useful for obtaining the object id and the data source identifier, an
2568 	 * ASCII-printable identifier for the data source that is intended to be
2569 	 * unique across multiple cases (e.g., a UUID). In the future, this method
2570 	 * will be a replacement for the getRootObjects method.
2571 	 *
2572 	 * @param objectId The object id of the data source.
2573 	 *
2574 	 * @return The data source.
2575 	 *
2576 	 * @throws TskDataException If there is no data source for the given object
2577 	 *                          id.
2578 	 * @throws TskCoreException If there is a problem getting the data source.
2579 	 */
getDataSource(long objectId)2580 	public DataSource getDataSource(long objectId) throws TskDataException, TskCoreException {
2581 		DataSource dataSource = null;
2582 		CaseDbConnection connection = connections.getConnection();
2583 		acquireSingleUserCaseReadLock();
2584 		Statement statement = null;
2585 		ResultSet resultSet = null;
2586 		Statement statement2 = null;
2587 		ResultSet resultSet2 = null;
2588 		try {
2589 			statement = connection.createStatement();
2590 			statement2 = connection.createStatement();
2591 			resultSet = connection.executeQuery(statement,
2592 					"SELECT ds.device_id, ds.time_zone, img.type, img.ssize, img.size, img.md5, img.sha1, img.sha256, img.display_name "
2593 					+ "FROM data_source_info AS ds "
2594 					+ "LEFT JOIN tsk_image_info AS img "
2595 					+ "ON ds.obj_id = img.obj_id "
2596 					+ "WHERE ds.obj_id = " + objectId); //NON-NLS
2597 			if (resultSet.next()) {
2598 				String deviceId = resultSet.getString("device_id");
2599 				String timezone = resultSet.getString("time_zone");
2600 				String type = resultSet.getString("type");
2601 
2602 				if (type == null) {
2603 					/*
2604 					 * No data found in 'tsk_image_info', so we build an
2605 					 * LocalFilesDataSource.
2606 					 */
2607 
2608 					resultSet2 = connection.executeQuery(statement2, "SELECT name FROM tsk_files WHERE tsk_files.obj_id = " + objectId); //NON-NLS
2609 					String dsName = (resultSet2.next()) ? resultSet2.getString("name") : "";
2610 
2611 					TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
2612 					TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
2613 					TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
2614 					final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
2615 							| TSK_FS_META_FLAG_ENUM.USED.getValue());
2616 					String parentPath = "/"; //NON-NLS
2617 					dataSource = new LocalFilesDataSource(this, objectId, objectId, deviceId, dsName, dirType, metaType, dirFlag, metaFlags, timezone, null, FileKnown.UNKNOWN, parentPath);
2618 				} else {
2619 					/*
2620 					 * Data found in 'tsk_image_info', so we build an Image.
2621 					 */
2622 					Long ssize = resultSet.getLong("ssize");
2623 					Long size = resultSet.getLong("size");
2624 					String md5 = resultSet.getString("md5");
2625 					String sha1 = resultSet.getString("sha1");
2626 					String sha256 = resultSet.getString("sha256");
2627 					String name = resultSet.getString("display_name");
2628 
2629 					List<String> imagePaths = getImagePathsById(objectId);
2630 					if (name == null) {
2631 						if (imagePaths.size() > 0) {
2632 							String path = imagePaths.get(0);
2633 							name = (new java.io.File(path)).getName();
2634 						} else {
2635 							name = "";
2636 						}
2637 					}
2638 
2639 					dataSource = new Image(this, objectId, Long.valueOf(type), deviceId, ssize, name,
2640 							imagePaths.toArray(new String[imagePaths.size()]), timezone, md5, sha1, sha256, size);
2641 				}
2642 			} else {
2643 				throw new TskDataException(String.format("There is no data source with obj_id = %d", objectId));
2644 			}
2645 		} catch (SQLException ex) {
2646 			throw new TskCoreException(String.format("Error getting data source with obj_id = %d", objectId), ex);
2647 		} finally {
2648 			closeResultSet(resultSet);
2649 			closeStatement(statement);
2650 			closeResultSet(resultSet2);
2651 			closeStatement(statement2);
2652 			connection.close();
2653 			releaseSingleUserCaseReadLock();
2654 		}
2655 
2656 		return dataSource;
2657 	}
2658 
2659 	/**
2660 	 * Get all blackboard artifacts of a given type. Does not included rejected
2661 	 * artifacts.
2662 	 *
2663 	 * @param artifactTypeID artifact type id (must exist in database)
2664 	 *
2665 	 * @return list of blackboard artifacts.
2666 	 *
2667 	 * @throws TskCoreException
2668 	 */
getBlackboardArtifacts(int artifactTypeID)2669 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(int artifactTypeID) throws TskCoreException {
2670 		return getArtifactsHelper("blackboard_artifacts.artifact_type_id = " + artifactTypeID);
2671 	}
2672 
2673 	/**
2674 	 * Get a count of blackboard artifacts for a given content. Does not include
2675 	 * rejected artifacts.
2676 	 *
2677 	 * @param objId Id of the content.
2678 	 *
2679 	 * @return The artifacts count for the content.
2680 	 *
2681 	 * @throws TskCoreException
2682 	 */
getBlackboardArtifactsCount(long objId)2683 	public long getBlackboardArtifactsCount(long objId) throws TskCoreException {
2684 		CaseDbConnection connection = connections.getConnection();
2685 		acquireSingleUserCaseReadLock();
2686 		ResultSet rs = null;
2687 		try {
2688 			// SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE obj_id = ?
2689 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_ARTIFACTS_FROM_SOURCE);
2690 			statement.clearParameters();
2691 			statement.setLong(1, objId);
2692 			rs = connection.executeQuery(statement);
2693 			long count = 0;
2694 			if (rs.next()) {
2695 				count = rs.getLong("count");
2696 			}
2697 			return count;
2698 		} catch (SQLException ex) {
2699 			throw new TskCoreException("Error getting number of blackboard artifacts by content", ex);
2700 		} finally {
2701 			closeResultSet(rs);
2702 			connection.close();
2703 			releaseSingleUserCaseReadLock();
2704 		}
2705 	}
2706 
2707 	/**
2708 	 * Get a count of artifacts of a given type. Does not include rejected
2709 	 * artifacts.
2710 	 *
2711 	 * @param artifactTypeID Id of the artifact type.
2712 	 *
2713 	 * @return The artifacts count for the type.
2714 	 *
2715 	 * @throws TskCoreException
2716 	 */
getBlackboardArtifactsTypeCount(int artifactTypeID)2717 	public long getBlackboardArtifactsTypeCount(int artifactTypeID) throws TskCoreException {
2718 		CaseDbConnection connection = connections.getConnection();
2719 		acquireSingleUserCaseReadLock();
2720 		ResultSet rs = null;
2721 		try {
2722 			// SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE artifact_type_id = ?
2723 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_ARTIFACTS_OF_TYPE);
2724 			statement.clearParameters();
2725 			statement.setInt(1, artifactTypeID);
2726 			rs = connection.executeQuery(statement);
2727 			long count = 0;
2728 			if (rs.next()) {
2729 				count = rs.getLong("count");
2730 			}
2731 			return count;
2732 		} catch (SQLException ex) {
2733 			throw new TskCoreException("Error getting number of blackboard artifacts by type", ex);
2734 		} finally {
2735 			closeResultSet(rs);
2736 			connection.close();
2737 			releaseSingleUserCaseReadLock();
2738 		}
2739 	}
2740 
2741 	/**
2742 	 * Get all blackboard artifacts that have an attribute of the given type and
2743 	 * String value. Does not included rejected artifacts.
2744 	 *
2745 	 * @param attrType attribute of this attribute type to look for in the
2746 	 *                 artifacts
2747 	 * @param value    value of the attribute of the attrType type to look for
2748 	 *
2749 	 * @return a list of blackboard artifacts with such an attribute
2750 	 *
2751 	 * @throws TskCoreException exception thrown if a critical error occurred
2752 	 *                          within tsk core and artifacts could not be
2753 	 *                          queried
2754 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, String value)2755 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, String value) throws TskCoreException {
2756 		CaseDbConnection connection = connections.getConnection();
2757 		acquireSingleUserCaseReadLock();
2758 		Statement s = null;
2759 		ResultSet rs = null;
2760 		try {
2761 			s = connection.createStatement();
2762 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
2763 					+ "arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
2764 					+ "types.type_name AS type_name, types.display_name AS display_name, "//NON-NLS
2765 					+ " arts.review_status_id AS review_status_id " //NON-NLS
2766 					+ "FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
2767 					+ "WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
2768 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
2769 					+ " AND attrs.value_text = '" + value + "'"
2770 					+ " AND types.artifact_type_id=arts.artifact_type_id"
2771 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());	 //NON-NLS
2772 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
2773 			while (rs.next()) {
2774 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
2775 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
2776 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
2777 			}
2778 			return artifacts;
2779 		} catch (SQLException ex) {
2780 			throw new TskCoreException("Error getting blackboard artifacts by attribute", ex);
2781 		} finally {
2782 			closeResultSet(rs);
2783 			closeStatement(s);
2784 			connection.close();
2785 			releaseSingleUserCaseReadLock();
2786 		}
2787 	}
2788 
2789 	/**
2790 	 * Get all blackboard artifacts that have an attribute of the given type and
2791 	 * String value. Does not included rejected artifacts.
2792 	 *
2793 	 * @param attrType   attribute of this attribute type to look for in the
2794 	 *                   artifacts
2795 	 * @param subString  value substring of the string attribute of the attrType
2796 	 *                   type to look for
2797 	 * @param startsWith if true, the artifact attribute string should start
2798 	 *                   with the substring, if false, it should just contain it
2799 	 *
2800 	 * @return a list of blackboard artifacts with such an attribute
2801 	 *
2802 	 * @throws TskCoreException exception thrown if a critical error occurred
2803 	 *                          within tsk core and artifacts could not be
2804 	 *                          queried
2805 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, String subString, boolean startsWith)2806 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, String subString, boolean startsWith) throws TskCoreException {
2807 		String valSubStr = "%" + subString; //NON-NLS
2808 		if (startsWith == false) {
2809 			valSubStr += "%"; //NON-NLS
2810 		}
2811 		CaseDbConnection connection = connections.getConnection();
2812 		acquireSingleUserCaseReadLock();
2813 		Statement s = null;
2814 		ResultSet rs = null;
2815 		try {
2816 			s = connection.createStatement();
2817 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
2818 					+ " arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, " //NON-NLS
2819 					+ " types.type_name AS type_name, types.display_name AS display_name, " //NON-NLS
2820 					+ " arts.review_status_id AS review_status_id " //NON-NLS
2821 					+ " FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
2822 					+ " WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
2823 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
2824 					+ " AND LOWER(attrs.value_text) LIKE LOWER('" + valSubStr + "')"
2825 					+ " AND types.artifact_type_id=arts.artifact_type_id "
2826 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
2827 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
2828 			while (rs.next()) {
2829 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
2830 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
2831 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
2832 			}
2833 			return artifacts;
2834 		} catch (SQLException ex) {
2835 			throw new TskCoreException("Error getting blackboard artifacts by attribute. " + ex.getMessage(), ex);
2836 		} finally {
2837 			closeResultSet(rs);
2838 			closeStatement(s);
2839 			connection.close();
2840 			releaseSingleUserCaseReadLock();
2841 		}
2842 	}
2843 
2844 	/**
2845 	 * Get all blackboard artifacts that have an attribute of the given type and
2846 	 * integer value. Does not included rejected artifacts.
2847 	 *
2848 	 * @param attrType attribute of this attribute type to look for in the
2849 	 *                 artifacts
2850 	 * @param value    value of the attribute of the attrType type to look for
2851 	 *
2852 	 * @return a list of blackboard artifacts with such an attribute
2853 	 *
2854 	 * @throws TskCoreException exception thrown if a critical error occurred
2855 	 *                          within tsk core and artifacts could not be
2856 	 *                          queried
2857 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, int value)2858 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, int value) throws TskCoreException {
2859 		CaseDbConnection connection = connections.getConnection();
2860 		acquireSingleUserCaseReadLock();
2861 		Statement s = null;
2862 		ResultSet rs = null;
2863 		try {
2864 			s = connection.createStatement();
2865 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
2866 					+ " arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
2867 					+ " types.type_name AS type_name, types.display_name AS display_name, "
2868 					+ " arts.review_status_id AS review_status_id  "//NON-NLS
2869 					+ " FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
2870 					+ "WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
2871 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
2872 					+ " AND attrs.value_int32 = " + value //NON-NLS
2873 					+ " AND types.artifact_type_id=arts.artifact_type_id "
2874 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
2875 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
2876 			while (rs.next()) {
2877 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
2878 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
2879 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
2880 			}
2881 			return artifacts;
2882 		} catch (SQLException ex) {
2883 			throw new TskCoreException("Error getting blackboard artifacts by attribute", ex);
2884 		} finally {
2885 			closeResultSet(rs);
2886 			closeStatement(s);
2887 			connection.close();
2888 			releaseSingleUserCaseReadLock();
2889 		}
2890 	}
2891 
2892 	/**
2893 	 * Get all blackboard artifacts that have an attribute of the given type and
2894 	 * long value. Does not included rejected artifacts.
2895 	 *
2896 	 * @param attrType attribute of this attribute type to look for in the
2897 	 *                 artifacts
2898 	 * @param value    value of the attribute of the attrType type to look for
2899 	 *
2900 	 * @return a list of blackboard artifacts with such an attribute
2901 	 *
2902 	 * @throws TskCoreException exception thrown if a critical error occurred
2903 	 *                          within tsk core and artifacts could not be
2904 	 *                          queried
2905 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, long value)2906 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, long value) throws TskCoreException {
2907 		CaseDbConnection connection = connections.getConnection();
2908 		acquireSingleUserCaseReadLock();
2909 		Statement s = null;
2910 		ResultSet rs = null;
2911 		try {
2912 			s = connection.createStatement();
2913 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
2914 					+ " arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
2915 					+ " types.type_name AS type_name, types.display_name AS display_name, "
2916 					+ " arts.review_status_id AS review_status_id "//NON-NLS
2917 					+ " FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
2918 					+ " WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
2919 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
2920 					+ " AND attrs.value_int64 = " + value //NON-NLS
2921 					+ " AND types.artifact_type_id=arts.artifact_type_id "
2922 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
2923 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
2924 			while (rs.next()) {
2925 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
2926 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
2927 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
2928 			}
2929 			return artifacts;
2930 		} catch (SQLException ex) {
2931 			throw new TskCoreException("Error getting blackboard artifacts by attribute. " + ex.getMessage(), ex);
2932 		} finally {
2933 			closeResultSet(rs);
2934 			closeStatement(s);
2935 			connection.close();
2936 			releaseSingleUserCaseReadLock();
2937 		}
2938 	}
2939 
2940 	/**
2941 	 * Get all blackboard artifacts that have an attribute of the given type and
2942 	 * double value. Does not included rejected artifacts.
2943 	 *
2944 	 * @param attrType attribute of this attribute type to look for in the
2945 	 *                 artifacts
2946 	 * @param value    value of the attribute of the attrType type to look for
2947 	 *
2948 	 * @return a list of blackboard artifacts with such an attribute
2949 	 *
2950 	 * @throws TskCoreException exception thrown if a critical error occurred
2951 	 *                          within tsk core and artifacts could not be
2952 	 *                          queried
2953 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, double value)2954 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, double value) throws TskCoreException {
2955 		CaseDbConnection connection = connections.getConnection();
2956 		acquireSingleUserCaseReadLock();
2957 		Statement s = null;
2958 		ResultSet rs = null;
2959 		try {
2960 			s = connection.createStatement();
2961 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
2962 					+ " arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
2963 					+ " types.type_name AS type_name, types.display_name AS display_name, "
2964 					+ " arts.review_status_id AS review_status_id "//NON-NLS
2965 					+ " FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
2966 					+ " WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
2967 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
2968 					+ " AND attrs.value_double = " + value //NON-NLS
2969 					+ " AND types.artifact_type_id=arts.artifact_type_id "
2970 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
2971 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
2972 			while (rs.next()) {
2973 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
2974 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
2975 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
2976 			}
2977 			return artifacts;
2978 		} catch (SQLException ex) {
2979 			throw new TskCoreException("Error getting blackboard artifacts by attribute", ex);
2980 		} finally {
2981 			closeResultSet(rs);
2982 			closeStatement(s);
2983 			connection.close();
2984 			releaseSingleUserCaseReadLock();
2985 		}
2986 	}
2987 
2988 	/**
2989 	 * Get all blackboard artifacts that have an attribute of the given type and
2990 	 * byte value. Does not include rejected artifacts.
2991 	 *
2992 	 * @param attrType attribute of this attribute type to look for in the
2993 	 *                 artifacts
2994 	 * @param value    value of the attribute of the attrType type to look for
2995 	 *
2996 	 * @return a list of blackboard artifacts with such an attribute
2997 	 *
2998 	 * @throws TskCoreException exception thrown if a critical error occurred
2999 	 *                          within tsk core and artifacts could not be
3000 	 *                          queried
3001 	 */
getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, byte value)3002 	public List<BlackboardArtifact> getBlackboardArtifacts(BlackboardAttribute.ATTRIBUTE_TYPE attrType, byte value) throws TskCoreException {
3003 		CaseDbConnection connection = connections.getConnection();
3004 		acquireSingleUserCaseReadLock();
3005 		Statement s = null;
3006 		ResultSet rs = null;
3007 		try {
3008 			s = connection.createStatement();
3009 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
3010 					+ " arts.obj_id AS obj_id, arts.artifact_obj_id AS artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
3011 					+ " types.type_name AS type_name, types.display_name AS display_name, "
3012 					+ " arts.review_status_id AS review_status_id "//NON-NLS
3013 					+ " FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
3014 					+ " WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
3015 					+ " AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
3016 					+ " AND attrs.value_byte = " + value //NON-NLS
3017 					+ " AND types.artifact_type_id=arts.artifact_type_id "
3018 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
3019 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
3020 			while (rs.next()) {
3021 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
3022 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
3023 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
3024 			}
3025 			return artifacts;
3026 		} catch (SQLException ex) {
3027 			throw new TskCoreException("Error getting blackboard artifacts by attribute", ex);
3028 		} finally {
3029 			closeResultSet(rs);
3030 			closeStatement(s);
3031 			connection.close();
3032 			releaseSingleUserCaseReadLock();
3033 		}
3034 	}
3035 
3036 	/**
3037 	 * Gets a list of all the artifact types for this case
3038 	 *
3039 	 * @return a list of artifact types
3040 	 *
3041 	 * @throws TskCoreException when there is an error getting the types
3042 	 */
getArtifactTypes()3043 	public Iterable<BlackboardArtifact.Type> getArtifactTypes() throws TskCoreException {
3044 		CaseDbConnection connection = connections.getConnection();
3045 		acquireSingleUserCaseReadLock();
3046 		Statement s = null;
3047 		ResultSet rs = null;
3048 		try {
3049 			s = connection.createStatement();
3050 			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name FROM blackboard_artifact_types"); //NON-NLS
3051 			ArrayList<BlackboardArtifact.Type> artifactTypes = new ArrayList<BlackboardArtifact.Type>();
3052 			while (rs.next()) {
3053 				artifactTypes.add(new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
3054 						rs.getString("type_name"), rs.getString("display_name")));
3055 			}
3056 			return artifactTypes;
3057 		} catch (SQLException ex) {
3058 			throw new TskCoreException("Error getting artifact types", ex); //NON-NLS
3059 		} finally {
3060 			closeResultSet(rs);
3061 			closeStatement(s);
3062 			connection.close();
3063 			releaseSingleUserCaseReadLock();
3064 		}
3065 	}
3066 
3067 	/**
3068 	 * Get all of the standard blackboard artifact types that are in use in the
3069 	 * blackboard.
3070 	 *
3071 	 * @return List of standard blackboard artifact types
3072 	 *
3073 	 * @throws TskCoreException
3074 	 */
getBlackboardArtifactTypesInUse()3075 	public ArrayList<BlackboardArtifact.ARTIFACT_TYPE> getBlackboardArtifactTypesInUse() throws TskCoreException {
3076 		String typeIdList = "";
3077 		for (int i = 0; i < BlackboardArtifact.ARTIFACT_TYPE.values().length; ++i) {
3078 			typeIdList += BlackboardArtifact.ARTIFACT_TYPE.values()[i].getTypeID();
3079 			if (i < BlackboardArtifact.ARTIFACT_TYPE.values().length - 1) {
3080 				typeIdList += ", ";
3081 			}
3082 		}
3083 		String query = "SELECT DISTINCT artifact_type_id FROM blackboard_artifacts "
3084 				+ "WHERE artifact_type_id IN (" + typeIdList + ")";
3085 		CaseDbConnection connection = connections.getConnection();
3086 		acquireSingleUserCaseReadLock();
3087 		Statement s = null;
3088 		ResultSet rs = null;
3089 		try {
3090 			s = connection.createStatement();
3091 			rs = connection.executeQuery(s, query);
3092 			ArrayList<BlackboardArtifact.ARTIFACT_TYPE> usedArts = new ArrayList<BlackboardArtifact.ARTIFACT_TYPE>();
3093 			while (rs.next()) {
3094 				usedArts.add(ARTIFACT_TYPE.fromID(rs.getInt("artifact_type_id")));
3095 			}
3096 			return usedArts;
3097 		} catch (SQLException ex) {
3098 			throw new TskCoreException("Error getting artifact types in use", ex);
3099 		} finally {
3100 			closeResultSet(rs);
3101 			closeStatement(s);
3102 			connection.close();
3103 			releaseSingleUserCaseReadLock();
3104 		}
3105 	}
3106 
3107 	/**
3108 	 * Gets the list of all unique artifact IDs in use.
3109 	 *
3110 	 * Gets both static and dynamic IDs.
3111 	 *
3112 	 * @return The list of unique IDs
3113 	 *
3114 	 * @throws TskCoreException exception thrown if a critical error occurred
3115 	 *                          within tsk core
3116 	 */
getArtifactTypesInUse()3117 	public List<BlackboardArtifact.Type> getArtifactTypesInUse() throws TskCoreException {
3118 		CaseDbConnection connection = connections.getConnection();
3119 		acquireSingleUserCaseReadLock();
3120 		Statement s = null;
3121 		ResultSet rs = null;
3122 		try {
3123 			s = connection.createStatement();
3124 			rs = connection.executeQuery(s,
3125 					"SELECT DISTINCT arts.artifact_type_id AS artifact_type_id, "
3126 					+ "types.type_name AS type_name, types.display_name AS display_name "
3127 					+ "FROM blackboard_artifact_types AS types "
3128 					+ "INNER JOIN blackboard_artifacts AS arts "
3129 					+ "ON arts.artifact_type_id = types.artifact_type_id"); //NON-NLS
3130 			List<BlackboardArtifact.Type> uniqueArtifactTypes = new ArrayList<BlackboardArtifact.Type>();
3131 			while (rs.next()) {
3132 				uniqueArtifactTypes.add(new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
3133 						rs.getString("type_name"), rs.getString("display_name")));
3134 			}
3135 			return uniqueArtifactTypes;
3136 		} catch (SQLException ex) {
3137 			throw new TskCoreException("Error getting attribute types", ex);
3138 		} finally {
3139 			closeResultSet(rs);
3140 			closeStatement(s);
3141 			connection.close();
3142 			releaseSingleUserCaseReadLock();
3143 		}
3144 	}
3145 
3146 	/**
3147 	 * Gets a list of all the attribute types for this case
3148 	 *
3149 	 * @return a list of attribute types
3150 	 *
3151 	 * @throws TskCoreException when there is an error getting the types
3152 	 */
getAttributeTypes()3153 	public List<BlackboardAttribute.Type> getAttributeTypes() throws TskCoreException {
3154 		CaseDbConnection connection = connections.getConnection();
3155 		acquireSingleUserCaseReadLock();
3156 		Statement s = null;
3157 		ResultSet rs = null;
3158 		try {
3159 			s = connection.createStatement();
3160 			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types"); //NON-NLS
3161 			ArrayList<BlackboardAttribute.Type> attribute_types = new ArrayList<BlackboardAttribute.Type>();
3162 			while (rs.next()) {
3163 				attribute_types.add(new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
3164 						rs.getString("display_name"), TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type"))));
3165 			}
3166 			return attribute_types;
3167 		} catch (SQLException ex) {
3168 			throw new TskCoreException("Error getting attribute types", ex);
3169 		} finally {
3170 			closeResultSet(rs);
3171 			closeStatement(s);
3172 			connection.close();
3173 			releaseSingleUserCaseReadLock();
3174 		}
3175 	}
3176 
3177 	/**
3178 	 * Get count of blackboard attribute types
3179 	 *
3180 	 * Counts both static (in enum) and dynamic attributes types (created by
3181 	 * modules at runtime)
3182 	 *
3183 	 * @return count of attribute types
3184 	 *
3185 	 * @throws TskCoreException exception thrown if a critical error occurs
3186 	 *                          within TSK core
3187 	 */
getBlackboardAttributeTypesCount()3188 	public int getBlackboardAttributeTypesCount() throws TskCoreException {
3189 		CaseDbConnection connection = connections.getConnection();
3190 		acquireSingleUserCaseReadLock();
3191 		Statement s = null;
3192 		ResultSet rs = null;
3193 		try {
3194 			s = connection.createStatement();
3195 			rs = connection.executeQuery(s, "SELECT COUNT(*) AS count FROM blackboard_attribute_types"); //NON-NLS
3196 			int count = 0;
3197 			if (rs.next()) {
3198 				count = rs.getInt("count");
3199 			}
3200 			return count;
3201 		} catch (SQLException ex) {
3202 			throw new TskCoreException("Error getting number of blackboard artifacts by type", ex);
3203 		} finally {
3204 			closeResultSet(rs);
3205 			closeStatement(s);
3206 			connection.close();
3207 			releaseSingleUserCaseReadLock();
3208 		}
3209 	}
3210 
3211 	/**
3212 	 * Gets unrejected blackboard artifacts that match a given WHERE clause.
3213 	 * Uses a SELECT	* statement that does a join of the blackboard_artifacts
3214 	 * and blackboard_artifact_types tables to get all of the required data.
3215 	 *
3216 	 * @param whereClause The WHERE clause to append to the SELECT statement.
3217 	 *
3218 	 * @return A list of BlackboardArtifact objects.
3219 	 *
3220 	 * @throws TskCoreException If there is a problem querying the case
3221 	 *                          database.
3222 	 */
getArtifactsHelper(String whereClause)3223 	ArrayList<BlackboardArtifact> getArtifactsHelper(String whereClause) throws TskCoreException {
3224 		CaseDbConnection connection = connections.getConnection();
3225 		acquireSingleUserCaseReadLock();
3226 		ResultSet rs = null;
3227 		try {
3228 			Statement statement = connection.createStatement();
3229 			String query = "SELECT blackboard_artifacts.artifact_id AS artifact_id, "
3230 					+ "blackboard_artifacts.obj_id AS obj_id, "
3231 					+ "blackboard_artifacts.artifact_obj_id AS artifact_obj_id, "
3232 					+ "blackboard_artifacts.data_source_obj_id AS data_source_obj_id, "
3233 					+ "blackboard_artifact_types.artifact_type_id AS artifact_type_id, "
3234 					+ "blackboard_artifact_types.type_name AS type_name, "
3235 					+ "blackboard_artifact_types.display_name AS display_name, "
3236 					+ "blackboard_artifacts.review_status_id AS review_status_id "
3237 					+ "FROM blackboard_artifacts, blackboard_artifact_types "
3238 					+ "WHERE blackboard_artifacts.artifact_type_id = blackboard_artifact_types.artifact_type_id "
3239 					+ " AND blackboard_artifacts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID()
3240 					+ " AND " + whereClause;
3241 			rs = connection.executeQuery(statement, query);
3242 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
3243 			while (rs.next()) {
3244 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
3245 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
3246 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
3247 			}
3248 			return artifacts;
3249 		} catch (SQLException ex) {
3250 			throw new TskCoreException("Error getting or creating a blackboard artifact", ex);
3251 		} finally {
3252 			closeResultSet(rs);
3253 			connection.close();
3254 			releaseSingleUserCaseReadLock();
3255 		}
3256 	}
3257 
3258 	/**
3259 	 * Helper method to get count of all artifacts matching the type id and
3260 	 * object id. Does not included rejected artifacts.
3261 	 *
3262 	 * @param artifactTypeID artifact type id
3263 	 * @param obj_id         associated object id
3264 	 *
3265 	 * @return count of matching blackboard artifacts
3266 	 *
3267 	 * @throws TskCoreException exception thrown if a critical error occurs
3268 	 *                          within TSK core
3269 	 */
getArtifactsCountHelper(int artifactTypeID, long obj_id)3270 	private long getArtifactsCountHelper(int artifactTypeID, long obj_id) throws TskCoreException {
3271 		CaseDbConnection connection = connections.getConnection();
3272 		acquireSingleUserCaseReadLock();
3273 		ResultSet rs = null;
3274 		try {
3275 			// SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE obj_id = ? AND artifact_type_id = ?
3276 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_ARTIFACTS_BY_SOURCE_AND_TYPE);
3277 			statement.clearParameters();
3278 			statement.setLong(1, obj_id);
3279 			statement.setInt(2, artifactTypeID);
3280 			rs = connection.executeQuery(statement);
3281 			long count = 0;
3282 			if (rs.next()) {
3283 				count = rs.getLong("count");
3284 			}
3285 			return count;
3286 		} catch (SQLException ex) {
3287 			throw new TskCoreException("Error getting blackboard artifact count", ex);
3288 		} finally {
3289 			closeResultSet(rs);
3290 			connection.close();
3291 			releaseSingleUserCaseReadLock();
3292 		}
3293 	}
3294 
3295 	/**
3296 	 * Get all blackboard artifacts of a given type for the given object id.
3297 	 * Does	not included rejected artifacts.
3298 	 *
3299 	 * @param artifactTypeName artifact type name
3300 	 * @param obj_id           object id
3301 	 *
3302 	 * @return list of blackboard artifacts
3303 	 *
3304 	 * @throws TskCoreException exception thrown if a critical error occurs
3305 	 *                          within TSK core
3306 	 */
getBlackboardArtifacts(String artifactTypeName, long obj_id)3307 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(String artifactTypeName, long obj_id) throws TskCoreException {
3308 		return getArtifactsHelper("blackboard_artifacts.obj_id = " + obj_id + " AND blackboard_artifact_types.type_name = '" + artifactTypeName + "';");
3309 	}
3310 
3311 	/**
3312 	 * Get all blackboard artifacts of a given type for the given object id.
3313 	 * Does not included rejected artifacts.
3314 	 *
3315 	 * @param artifactTypeID artifact type id (must exist in database)
3316 	 * @param obj_id         object id
3317 	 *
3318 	 * @return list of blackboard artifacts
3319 	 *
3320 	 * @throws TskCoreException exception thrown if a critical error occurs
3321 	 *                          within TSK core
3322 	 */
getBlackboardArtifacts(int artifactTypeID, long obj_id)3323 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(int artifactTypeID, long obj_id) throws TskCoreException {
3324 		return getArtifactsHelper("blackboard_artifacts.obj_id = " + obj_id + " AND blackboard_artifact_types.artifact_type_id = " + artifactTypeID + ";");
3325 	}
3326 
3327 	/**
3328 	 * Get all blackboard artifacts of a given type for the given object id.
3329 	 * Does not included rejected artifacts.
3330 	 *
3331 	 * @param artifactType artifact type enum
3332 	 * @param obj_id       object id
3333 	 *
3334 	 * @return list of blackboard artifacts
3335 	 *
3336 	 * @throws TskCoreException exception thrown if a critical error occurs
3337 	 *                          within TSK core
3338 	 */
getBlackboardArtifacts(ARTIFACT_TYPE artifactType, long obj_id)3339 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(ARTIFACT_TYPE artifactType, long obj_id) throws TskCoreException {
3340 		return getBlackboardArtifacts(artifactType.getTypeID(), obj_id);
3341 	}
3342 
3343 	/**
3344 	 * Get count of all blackboard artifacts of a given type for the given
3345 	 * object id. Does not include rejected artifacts.
3346 	 *
3347 	 * @param artifactTypeName artifact type name
3348 	 * @param obj_id           object id
3349 	 *
3350 	 * @return count of blackboard artifacts
3351 	 *
3352 	 * @throws TskCoreException exception thrown if a critical error occurs
3353 	 *                          within TSK core
3354 	 */
getBlackboardArtifactsCount(String artifactTypeName, long obj_id)3355 	public long getBlackboardArtifactsCount(String artifactTypeName, long obj_id) throws TskCoreException {
3356 		int artifactTypeID = this.getArtifactType(artifactTypeName).getTypeID();
3357 		if (artifactTypeID == -1) {
3358 			return 0;
3359 		}
3360 		return getArtifactsCountHelper(artifactTypeID, obj_id);
3361 	}
3362 
3363 	/**
3364 	 * Get count of all blackboard artifacts of a given type for the given
3365 	 * object id. Does not include rejected artifacts.
3366 	 *
3367 	 * @param artifactTypeID artifact type id (must exist in database)
3368 	 * @param obj_id         object id
3369 	 *
3370 	 * @return count of blackboard artifacts
3371 	 *
3372 	 * @throws TskCoreException exception thrown if a critical error occurs
3373 	 *                          within TSK core
3374 	 */
getBlackboardArtifactsCount(int artifactTypeID, long obj_id)3375 	public long getBlackboardArtifactsCount(int artifactTypeID, long obj_id) throws TskCoreException {
3376 		return getArtifactsCountHelper(artifactTypeID, obj_id);
3377 	}
3378 
3379 	/**
3380 	 * Get count of all blackboard artifacts of a given type for the given
3381 	 * object id. Does not include rejected artifacts.
3382 	 *
3383 	 * @param artifactType artifact type enum
3384 	 * @param obj_id       object id
3385 	 *
3386 	 * @return count of blackboard artifacts
3387 	 *
3388 	 * @throws TskCoreException exception thrown if a critical error occurs
3389 	 *                          within TSK core
3390 	 */
getBlackboardArtifactsCount(ARTIFACT_TYPE artifactType, long obj_id)3391 	public long getBlackboardArtifactsCount(ARTIFACT_TYPE artifactType, long obj_id) throws TskCoreException {
3392 		return getArtifactsCountHelper(artifactType.getTypeID(), obj_id);
3393 	}
3394 
3395 	/**
3396 	 * Get all blackboard artifacts of a given type. Does not included rejected
3397 	 * artifacts.
3398 	 *
3399 	 * @param artifactTypeName artifact type name
3400 	 *
3401 	 * @return list of blackboard artifacts
3402 	 *
3403 	 * @throws TskCoreException exception thrown if a critical error occurs
3404 	 *                          within TSK core
3405 	 */
getBlackboardArtifacts(String artifactTypeName)3406 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(String artifactTypeName) throws TskCoreException {
3407 		return getArtifactsHelper("blackboard_artifact_types.type_name = '" + artifactTypeName + "';");
3408 	}
3409 
3410 	/**
3411 	 * Get all blackboard artifacts of a given type. Does not included rejected
3412 	 * artifacts.
3413 	 *
3414 	 * @param artifactType artifact type enum
3415 	 *
3416 	 * @return list of blackboard artifacts
3417 	 *
3418 	 * @throws TskCoreException exception thrown if a critical error occurs
3419 	 *                          within TSK core
3420 	 */
getBlackboardArtifacts(ARTIFACT_TYPE artifactType)3421 	public ArrayList<BlackboardArtifact> getBlackboardArtifacts(ARTIFACT_TYPE artifactType) throws TskCoreException {
3422 		return getArtifactsHelper("blackboard_artifact_types.artifact_type_id = " + artifactType.getTypeID() + ";");
3423 	}
3424 
3425 	/**
3426 	 * Get all blackboard artifacts of a given type with an attribute of a given
3427 	 * type and String value. Does not included rejected artifacts.
3428 	 *
3429 	 * @param artifactType artifact type enum
3430 	 * @param attrType     attribute type enum
3431 	 * @param value        String value of attribute
3432 	 *
3433 	 * @return list of blackboard artifacts
3434 	 *
3435 	 * @throws TskCoreException exception thrown if a critical error occurs
3436 	 *                          within TSK core
3437 	 */
getBlackboardArtifacts(ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE attrType, String value)3438 	public List<BlackboardArtifact> getBlackboardArtifacts(ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE attrType, String value) throws TskCoreException {
3439 		CaseDbConnection connection = connections.getConnection();
3440 		acquireSingleUserCaseReadLock();
3441 		Statement s = null;
3442 		ResultSet rs = null;
3443 		try {
3444 			s = connection.createStatement();
3445 			rs = connection.executeQuery(s, "SELECT DISTINCT arts.artifact_id AS artifact_id, " //NON-NLS
3446 					+ "arts.obj_id AS obj_id, arts.artifact_obj_id as artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
3447 					+ "types.type_name AS type_name, types.display_name AS display_name,"
3448 					+ "arts.review_status_id AS review_status_id "//NON-NLS
3449 					+ "FROM blackboard_artifacts AS arts, blackboard_attributes AS attrs, blackboard_artifact_types AS types " //NON-NLS
3450 					+ "WHERE arts.artifact_id = attrs.artifact_id " //NON-NLS
3451 					+ "AND attrs.attribute_type_id = " + attrType.getTypeID() //NON-NLS
3452 					+ " AND arts.artifact_type_id = " + artifactType.getTypeID() //NON-NLS
3453 					+ " AND attrs.value_text = '" + value + "'" //NON-NLS
3454 					+ " AND types.artifact_type_id=arts.artifact_type_id"
3455 					+ " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID());
3456 			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
3457 			while (rs.next()) {
3458 				artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
3459 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
3460 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
3461 			}
3462 			return artifacts;
3463 		} catch (SQLException ex) {
3464 			throw new TskCoreException("Error getting blackboard artifacts by artifact type and attribute. " + ex.getMessage(), ex);
3465 		} finally {
3466 			closeResultSet(rs);
3467 			closeStatement(s);
3468 			connection.close();
3469 			releaseSingleUserCaseReadLock();
3470 		}
3471 	}
3472 
3473 	/**
3474 	 * Get the blackboard artifact with the given artifact id
3475 	 *
3476 	 * @param artifactID artifact ID
3477 	 *
3478 	 * @return blackboard artifact
3479 	 *
3480 	 * @throws TskCoreException exception thrown if a critical error occurs
3481 	 *                          within TSK core
3482 	 */
getBlackboardArtifact(long artifactID)3483 	public BlackboardArtifact getBlackboardArtifact(long artifactID) throws TskCoreException {
3484 		CaseDbConnection connection = connections.getConnection();
3485 		acquireSingleUserCaseReadLock();
3486 		ResultSet rs = null;
3487 		Statement s;
3488 		try {
3489 			s = connection.createStatement();
3490 			rs = connection.executeQuery(s, "SELECT arts.artifact_id AS artifact_id, "
3491 					+ "arts.obj_id AS obj_id, arts.artifact_obj_id as artifact_obj_id, arts.data_source_obj_id AS data_source_obj_id, arts.artifact_type_id AS artifact_type_id, "
3492 					+ "types.type_name AS type_name, types.display_name AS display_name,"
3493 					+ "arts.review_status_id AS review_status_id "//NON-NLS
3494 					+ "FROM blackboard_artifacts AS arts, blackboard_artifact_types AS types "
3495 					+ "WHERE arts.artifact_id = " + artifactID
3496 					+ " AND arts.artifact_type_id = types.artifact_type_id");
3497 			if (rs.next()) {
3498 				return new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
3499 						rs.getInt("artifact_type_id"), rs.getString("type_name"), rs.getString("display_name"),
3500 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id")));
3501 			} else {
3502 				/*
3503 				 * I think this should actually return null (or Optional) when
3504 				 * there is no artifact with the given id, but it looks like
3505 				 * existing code is not expecting that. -jm
3506 				 */
3507 				throw new TskCoreException("No blackboard artifact with id " + artifactID);
3508 			}
3509 		} catch (SQLException ex) {
3510 			throw new TskCoreException("Error getting a blackboard artifact. " + ex.getMessage(), ex);
3511 		} finally {
3512 			closeResultSet(rs);
3513 			connection.close();
3514 			releaseSingleUserCaseReadLock();
3515 		}
3516 	}
3517 
3518 	/**
3519 	 * Add a blackboard attribute.
3520 	 *
3521 	 * @param attr           A blackboard attribute.
3522 	 * @param artifactTypeId The type of artifact associated with the attribute.
3523 	 *
3524 	 * @throws TskCoreException thrown if a critical error occurs.
3525 	 */
addBlackboardAttribute(BlackboardAttribute attr, int artifactTypeId)3526 	public void addBlackboardAttribute(BlackboardAttribute attr, int artifactTypeId) throws TskCoreException {
3527 		CaseDbConnection connection = connections.getConnection();
3528 		acquireSingleUserCaseWriteLock();
3529 		try {
3530 			addBlackBoardAttribute(attr, artifactTypeId, connection);
3531 		} catch (SQLException ex) {
3532 			throw new TskCoreException("Error adding blackboard attribute " + attr.toString(), ex);
3533 		} finally {
3534 			connection.close();
3535 			releaseSingleUserCaseWriteLock();
3536 		}
3537 	}
3538 
3539 	/**
3540 	 * Add a set blackboard attributes.
3541 	 *
3542 	 * @param attributes     A set of blackboard attribute.
3543 	 * @param artifactTypeId The type of artifact associated with the
3544 	 *                       attributes.
3545 	 *
3546 	 * @throws TskCoreException thrown if a critical error occurs.
3547 	 */
addBlackboardAttributes(Collection<BlackboardAttribute> attributes, int artifactTypeId)3548 	public void addBlackboardAttributes(Collection<BlackboardAttribute> attributes, int artifactTypeId) throws TskCoreException {
3549 		CaseDbConnection connection = connections.getConnection();
3550 		acquireSingleUserCaseWriteLock();
3551 		try {
3552 			connection.beginTransaction();
3553 			for (final BlackboardAttribute attr : attributes) {
3554 				addBlackBoardAttribute(attr, artifactTypeId, connection);
3555 			}
3556 			connection.commitTransaction();
3557 		} catch (SQLException ex) {
3558 			connection.rollbackTransaction();
3559 			throw new TskCoreException("Error adding blackboard attributes", ex);
3560 		} finally {
3561 			connection.close();
3562 			releaseSingleUserCaseWriteLock();
3563 		}
3564 	}
3565 
addBlackBoardAttribute(BlackboardAttribute attr, int artifactTypeId, CaseDbConnection connection)3566 	private void addBlackBoardAttribute(BlackboardAttribute attr, int artifactTypeId, CaseDbConnection connection) throws SQLException, TskCoreException {
3567 		PreparedStatement statement;
3568 		switch (attr.getAttributeType().getValueType()) {
3569 			case STRING:
3570 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_STRING_ATTRIBUTE);
3571 				statement.clearParameters();
3572 				statement.setString(7, attr.getValueString());
3573 				break;
3574 			case BYTE:
3575 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_BYTE_ATTRIBUTE);
3576 				statement.clearParameters();
3577 				statement.setBytes(7, attr.getValueBytes());
3578 				break;
3579 			case INTEGER:
3580 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_INT_ATTRIBUTE);
3581 				statement.clearParameters();
3582 				statement.setInt(7, attr.getValueInt());
3583 				break;
3584 			case LONG:
3585 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LONG_ATTRIBUTE);
3586 				statement.clearParameters();
3587 				statement.setLong(7, attr.getValueLong());
3588 				break;
3589 			case DOUBLE:
3590 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_DOUBLE_ATTRIBUTE);
3591 				statement.clearParameters();
3592 				statement.setDouble(7, attr.getValueDouble());
3593 				break;
3594 			case DATETIME:
3595 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LONG_ATTRIBUTE);
3596 				statement.clearParameters();
3597 				statement.setLong(7, attr.getValueLong());
3598 				break;
3599 			default:
3600 				throw new TskCoreException("Unrecognized artifact attribute value type");
3601 		}
3602 		statement.setLong(1, attr.getArtifactID());
3603 		statement.setInt(2, artifactTypeId);
3604 		statement.setString(3, attr.getSourcesCSV());
3605 		statement.setString(4, "");
3606 		statement.setInt(5, attr.getAttributeType().getTypeID());
3607 		statement.setLong(6, attr.getAttributeType().getValueType().getType());
3608 		connection.executeUpdate(statement);
3609 	}
3610 
3611 	/**
3612 	 * Adds a source name to the source column of one or more rows in the
3613 	 * blackboard attributes table. The source name will be added to a CSV list
3614 	 * in any rows that exactly match the attribute's artifact_id and value.
3615 	 *
3616 	 * @param attr   The artifact attribute
3617 	 * @param source The source name.
3618 	 *
3619 	 * @throws TskCoreException
3620 	 */
addSourceToArtifactAttribute(BlackboardAttribute attr, String source)3621 	String addSourceToArtifactAttribute(BlackboardAttribute attr, String source) throws TskCoreException {
3622 		/*
3623 		 * WARNING: This is a temporary implementation that is not safe and
3624 		 * denormalizes the case datbase.
3625 		 *
3626 		 * TODO (JIRA-2294): Provide a safe and normalized solution to tracking
3627 		 * the sources of artifact attributes.
3628 		 */
3629 		if (null == source || source.isEmpty()) {
3630 			throw new TskCoreException("Attempt to add null or empty source module name to artifact attribute");
3631 		}
3632 		CaseDbConnection connection = connections.getConnection();
3633 		acquireSingleUserCaseWriteLock();
3634 		Statement queryStmt = null;
3635 		Statement updateStmt = null;
3636 		ResultSet result = null;
3637 		String newSources = "";
3638 		try {
3639 			connection.beginTransaction();
3640 			String valueClause = "";
3641 			BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType = attr.getAttributeType().getValueType();
3642 			if (BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE != valueType) {
3643 				switch (valueType) {
3644 					case STRING:
3645 						valueClause = " value_text = '" + escapeSingleQuotes(attr.getValueString()) + "'";
3646 						break;
3647 					case INTEGER:
3648 						valueClause = " value_int32 = " + attr.getValueInt();
3649 						break;
3650 					case LONG:
3651 					case DATETIME:
3652 						valueClause = " value_int64 = " + attr.getValueLong();
3653 						break;
3654 					case DOUBLE:
3655 						valueClause = " value_double = " + attr.getValueDouble();
3656 						break;
3657 					default:
3658 						throw new TskCoreException(String.format("Unrecognized value type for attribute %s", attr.getDisplayString()));
3659 				}
3660 				String query = "SELECT source FROM blackboard_attributes WHERE"
3661 						+ " artifact_id = " + attr.getArtifactID()
3662 						+ " AND attribute_type_id = " + attr.getAttributeType().getTypeID()
3663 						+ " AND value_type = " + attr.getAttributeType().getValueType().getType()
3664 						+ " AND " + valueClause + ";";
3665 				queryStmt = connection.createStatement();
3666 				updateStmt = connection.createStatement();
3667 				result = connection.executeQuery(queryStmt, query);
3668 			} else {
3669 				/*
3670 				 * SELECT source FROM blackboard_attributes WHERE artifact_id =
3671 				 * ? AND attribute_type_id = ? AND value_type = 4 AND value_byte
3672 				 * = ?
3673 				 */
3674 				PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ATTR_BY_VALUE_BYTE);
3675 				statement.clearParameters();
3676 				statement.setLong(1, attr.getArtifactID());
3677 				statement.setLong(2, attr.getAttributeType().getTypeID());
3678 				statement.setBytes(3, attr.getValueBytes());
3679 				result = connection.executeQuery(statement);
3680 			}
3681 			while (result.next()) {
3682 				String oldSources = result.getString("source");
3683 				if (null != oldSources && !oldSources.isEmpty()) {
3684 					Set<String> uniqueSources = new HashSet<String>(Arrays.asList(oldSources.split(",")));
3685 					if (!uniqueSources.contains(source)) {
3686 						newSources = oldSources + "," + source;
3687 					} else {
3688 						newSources = oldSources;
3689 					}
3690 				} else {
3691 					newSources = source;
3692 				}
3693 				if (BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE != valueType) {
3694 					String update = "UPDATE blackboard_attributes SET source = '" + newSources + "' WHERE"
3695 							+ " artifact_id = " + attr.getArtifactID()
3696 							+ " AND attribute_type_id = " + attr.getAttributeType().getTypeID()
3697 							+ " AND value_type = " + attr.getAttributeType().getValueType().getType()
3698 							+ " AND " + valueClause + ";";
3699 					connection.executeUpdate(updateStmt, update);
3700 				} else {
3701 					/*
3702 					 * UPDATE blackboard_attributes SET source = ? WHERE
3703 					 * artifact_id = ? AND attribute_type_id = ? AND value_type
3704 					 * = 4 AND value_byte = ?
3705 					 */
3706 					PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_ATTR_BY_VALUE_BYTE);
3707 					statement.clearParameters();
3708 					statement.setString(1, newSources);
3709 					statement.setLong(2, attr.getArtifactID());
3710 					statement.setLong(3, attr.getAttributeType().getTypeID());
3711 					statement.setBytes(4, attr.getValueBytes());
3712 					connection.executeUpdate(statement);
3713 				}
3714 			}
3715 			connection.commitTransaction();
3716 			return newSources;
3717 		} catch (SQLException ex) {
3718 			connection.rollbackTransaction();
3719 			throw new TskCoreException(String.format("Error adding source module to attribute %s", attr.getDisplayString()), ex);
3720 		} finally {
3721 			closeResultSet(result);
3722 			closeStatement(updateStmt);
3723 			closeStatement(queryStmt);
3724 			connection.close();
3725 			releaseSingleUserCaseWriteLock();
3726 		}
3727 	}
3728 
3729 	/**
3730 	 * Add an attribute type with the given name
3731 	 *
3732 	 * @param attrTypeString Name of the new attribute
3733 	 * @param valueType      The value type of this new attribute type
3734 	 * @param displayName    The (non-unique) display name of the attribute type
3735 	 *
3736 	 * @return the id of the new attribute
3737 	 *
3738 	 * @throws TskCoreException exception thrown if a critical error occurs
3739 	 *                          within tsk core
3740 	 * @throws TskDataException exception thrown if attribute type was already
3741 	 *                          in the system
3742 	 */
addArtifactAttributeType(String attrTypeString, TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName)3743 	public BlackboardAttribute.Type addArtifactAttributeType(String attrTypeString, TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws TskCoreException, TskDataException {
3744 		CaseDbConnection connection = connections.getConnection();
3745 		acquireSingleUserCaseWriteLock();
3746 		Statement s = null;
3747 		ResultSet rs = null;
3748 		try {
3749 			connection.beginTransaction();
3750 			s = connection.createStatement();
3751 			rs = connection.executeQuery(s, "SELECT attribute_type_id FROM blackboard_attribute_types WHERE type_name = '" + attrTypeString + "'"); //NON-NLS
3752 			if (!rs.next()) {
3753 				rs.close();
3754 				rs = connection.executeQuery(s, "SELECT MAX(attribute_type_id) AS highest_id FROM blackboard_attribute_types");
3755 				int maxID = 0;
3756 				if (rs.next()) {
3757 					maxID = rs.getInt("highest_id");
3758 					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
3759 						maxID = MIN_USER_DEFINED_TYPE_ID;
3760 					} else {
3761 						maxID++;
3762 					}
3763 				}
3764 				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
3765 				BlackboardAttribute.Type type = new BlackboardAttribute.Type(maxID, attrTypeString, displayName, valueType);
3766 				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
3767 				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
3768 				connection.commitTransaction();
3769 				return type;
3770 			} else {
3771 				throw new TskDataException("The attribute type that was added was already within the system.");
3772 			}
3773 
3774 		} catch (SQLException ex) {
3775 			connection.rollbackTransaction();
3776 			throw new TskCoreException("Error adding attribute type", ex);
3777 		} finally {
3778 			closeResultSet(rs);
3779 			closeStatement(s);
3780 			connection.close();
3781 			releaseSingleUserCaseWriteLock();
3782 		}
3783 	}
3784 
3785 	/**
3786 	 * Get the attribute type associated with an attribute type name.
3787 	 *
3788 	 * @param attrTypeName An attribute type name.
3789 	 *
3790 	 * @return An attribute type or null if the attribute type does not exist.
3791 	 *
3792 	 * @throws TskCoreException If an error occurs accessing the case database.
3793 	 *
3794 	 */
getAttributeType(String attrTypeName)3795 	public BlackboardAttribute.Type getAttributeType(String attrTypeName) throws TskCoreException {
3796 		if (this.typeNameToAttributeTypeMap.containsKey(attrTypeName)) {
3797 			return this.typeNameToAttributeTypeMap.get(attrTypeName);
3798 		}
3799 		CaseDbConnection connection = connections.getConnection();
3800 		acquireSingleUserCaseReadLock();
3801 		Statement s = null;
3802 		ResultSet rs = null;
3803 		try {
3804 			s = connection.createStatement();
3805 			rs = connection.executeQuery(s, "SELECT attribute_type_id, type_name, display_name, value_type FROM blackboard_attribute_types WHERE type_name = '" + attrTypeName + "'"); //NON-NLS
3806 			BlackboardAttribute.Type type = null;
3807 			if (rs.next()) {
3808 				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
3809 						rs.getString("display_name"), TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
3810 				this.typeIdToAttributeTypeMap.put(type.getTypeID(), type);
3811 				this.typeNameToAttributeTypeMap.put(attrTypeName, type);
3812 			}
3813 			return type;
3814 		} catch (SQLException ex) {
3815 			throw new TskCoreException("Error getting attribute type id", ex);
3816 		} finally {
3817 			closeResultSet(rs);
3818 			closeStatement(s);
3819 			connection.close();
3820 			releaseSingleUserCaseReadLock();
3821 		}
3822 	}
3823 
3824 	/**
3825 	 * Get the attribute type associated with an attribute type ID.
3826 	 *
3827 	 * @param typeID An attribute type ID.
3828 	 *
3829 	 * @return An attribute type or null if the attribute type does not exist.
3830 	 *
3831 	 * @throws TskCoreException If an error occurs accessing the case database.
3832 	 *
3833 	 */
getAttributeType(int typeID)3834 	private BlackboardAttribute.Type getAttributeType(int typeID) throws TskCoreException {
3835 		if (this.typeIdToAttributeTypeMap.containsKey(typeID)) {
3836 			return this.typeIdToAttributeTypeMap.get(typeID);
3837 		}
3838 		CaseDbConnection connection = connections.getConnection();
3839 		acquireSingleUserCaseReadLock();
3840 		Statement s = null;
3841 		ResultSet rs = null;
3842 		try {
3843 			s = connection.createStatement();
3844 			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
3845 			BlackboardAttribute.Type type = null;
3846 			if (rs.next()) {
3847 				type = new BlackboardAttribute.Type(rs.getInt("attribute_type_id"), rs.getString("type_name"),
3848 						rs.getString("display_name"), TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getLong("value_type")));
3849 				this.typeIdToAttributeTypeMap.put(typeID, type);
3850 				this.typeNameToAttributeTypeMap.put(type.getTypeName(), type);
3851 			}
3852 			return type;
3853 		} catch (SQLException ex) {
3854 			throw new TskCoreException("Error getting attribute type id", ex);
3855 		} finally {
3856 			closeResultSet(rs);
3857 			closeStatement(s);
3858 			connection.close();
3859 			releaseSingleUserCaseReadLock();
3860 		}
3861 	}
3862 
3863 	/**
3864 	 * Get the artifact type associated with an artifact type name.
3865 	 *
3866 	 * @param artTypeName An artifact type name.
3867 	 *
3868 	 * @return An artifact type or null if the artifact type does not exist.
3869 	 *
3870 	 * @throws TskCoreException If an error occurs accessing the case database.
3871 	 *
3872 	 */
getArtifactType(String artTypeName)3873 	public BlackboardArtifact.Type getArtifactType(String artTypeName) throws TskCoreException {
3874 		if (this.typeNameToArtifactTypeMap.containsKey(artTypeName)) {
3875 			return this.typeNameToArtifactTypeMap.get(artTypeName);
3876 		}
3877 		CaseDbConnection connection = connections.getConnection();
3878 		acquireSingleUserCaseReadLock();
3879 		Statement s = null;
3880 		ResultSet rs = null;
3881 		try {
3882 			s = connection.createStatement();
3883 			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name FROM blackboard_artifact_types WHERE type_name = '" + artTypeName + "'"); //NON-NLS
3884 			BlackboardArtifact.Type type = null;
3885 			if (rs.next()) {
3886 				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
3887 						rs.getString("type_name"), rs.getString("display_name"));
3888 				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
3889 				this.typeNameToArtifactTypeMap.put(artTypeName, type);
3890 			}
3891 			return type;
3892 		} catch (SQLException ex) {
3893 			throw new TskCoreException("Error getting artifact type from the database", ex);
3894 		} finally {
3895 			closeResultSet(rs);
3896 			closeStatement(s);
3897 			connection.close();
3898 			releaseSingleUserCaseReadLock();
3899 		}
3900 	}
3901 
3902 	/**
3903 	 * Get the artifact type associated with an artifact type id.
3904 	 *
3905 	 * @param artTypeId An artifact type id.
3906 	 *
3907 	 * @return An artifact type or null if the artifact type does not exist.
3908 	 *
3909 	 * @throws TskCoreException If an error occurs accessing the case database.
3910 	 *
3911 	 */
getArtifactType(int artTypeId)3912 	BlackboardArtifact.Type getArtifactType(int artTypeId) throws TskCoreException {
3913 		if (this.typeIdToArtifactTypeMap.containsKey(artTypeId)) {
3914 			return typeIdToArtifactTypeMap.get(artTypeId);
3915 		}
3916 		CaseDbConnection connection = connections.getConnection();
3917 		acquireSingleUserCaseReadLock();
3918 		Statement s = null;
3919 		ResultSet rs = null;
3920 		try {
3921 			s = connection.createStatement();
3922 			rs = connection.executeQuery(s, "SELECT artifact_type_id, type_name, display_name FROM blackboard_artifact_types WHERE artifact_type_id = " + artTypeId + ""); //NON-NLS
3923 			BlackboardArtifact.Type type = null;
3924 			if (rs.next()) {
3925 				type = new BlackboardArtifact.Type(rs.getInt("artifact_type_id"),
3926 						rs.getString("type_name"), rs.getString("display_name"));
3927 				this.typeIdToArtifactTypeMap.put(artTypeId, type);
3928 				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
3929 			}
3930 			return type;
3931 		} catch (SQLException ex) {
3932 			throw new TskCoreException("Error getting artifact type from the database", ex);
3933 		} finally {
3934 			closeResultSet(rs);
3935 			closeStatement(s);
3936 			connection.close();
3937 			releaseSingleUserCaseReadLock();
3938 		}
3939 	}
3940 
3941 	/**
3942 	 * Add an artifact type with the given name. Will return an artifact Type.
3943 	 *
3944 	 * @param artifactTypeName System (unique) name of artifact
3945 	 * @param displayName      Display (non-unique) name of artifact
3946 	 *
3947 	 * @return Type of the artifact added
3948 	 *
3949 	 * @throws TskCoreException exception thrown if a critical error occurs
3950 	 * @throws TskDataException exception thrown if given data is already in db
3951 	 *                          within tsk core
3952 	 */
addBlackboardArtifactType(String artifactTypeName, String displayName)3953 	public BlackboardArtifact.Type addBlackboardArtifactType(String artifactTypeName, String displayName) throws TskCoreException, TskDataException {
3954 		CaseDbConnection connection = connections.getConnection();
3955 		acquireSingleUserCaseWriteLock();
3956 		Statement s = null;
3957 		ResultSet rs = null;
3958 		try {
3959 			connection.beginTransaction();
3960 			s = connection.createStatement();
3961 			rs = connection.executeQuery(s, "SELECT artifact_type_id FROM blackboard_artifact_types WHERE type_name = '" + artifactTypeName + "'"); //NON-NLS
3962 			if (!rs.next()) {
3963 				rs.close();
3964 				rs = connection.executeQuery(s, "SELECT MAX(artifact_type_id) AS highest_id FROM blackboard_artifact_types");
3965 				int maxID = 0;
3966 				if (rs.next()) {
3967 					maxID = rs.getInt("highest_id");
3968 					if (maxID < MIN_USER_DEFINED_TYPE_ID) {
3969 						maxID = MIN_USER_DEFINED_TYPE_ID;
3970 					} else {
3971 						maxID++;
3972 					}
3973 				}
3974 				connection.executeUpdate(s, "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name) VALUES ('" + maxID + "', '" + artifactTypeName + "', '" + displayName + "')"); //NON-NLS
3975 				BlackboardArtifact.Type type = new BlackboardArtifact.Type(maxID, artifactTypeName, displayName);
3976 				this.typeIdToArtifactTypeMap.put(type.getTypeID(), type);
3977 				this.typeNameToArtifactTypeMap.put(type.getTypeName(), type);
3978 				connection.commitTransaction();
3979 				return type;
3980 			} else {
3981 				throw new TskDataException("The attribute type that was added was already within the system.");
3982 			}
3983 		} catch (SQLException ex) {
3984 			connection.rollbackTransaction();
3985 			throw new TskCoreException("Error adding artifact type", ex);
3986 		} finally {
3987 			closeResultSet(rs);
3988 			closeStatement(s);
3989 			connection.close();
3990 			releaseSingleUserCaseWriteLock();
3991 		}
3992 	}
3993 
getBlackboardAttributes(final BlackboardArtifact artifact)3994 	public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardArtifact artifact) throws TskCoreException {
3995 		CaseDbConnection connection = connections.getConnection();
3996 		acquireSingleUserCaseReadLock();
3997 		ResultSet rs = null;
3998 		try {
3999 			Statement statement = connection.createStatement();
4000 			rs = connection.executeQuery(statement, "SELECT attrs.artifact_id AS artifact_id, "
4001 					+ "attrs.source AS source, attrs.context AS context, attrs.attribute_type_id AS attribute_type_id, "
4002 					+ "attrs.value_type AS value_type, attrs.value_byte AS value_byte, "
4003 					+ "attrs.value_text AS value_text, attrs.value_int32 AS value_int32, "
4004 					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
4005 					+ "types.type_name AS type_name, types.display_name AS display_name "
4006 					+ "FROM blackboard_attributes AS attrs, blackboard_attribute_types AS types WHERE attrs.artifact_id = " + artifact.getArtifactID()
4007 					+ " AND attrs.attribute_type_id = types.attribute_type_id");
4008 			ArrayList<BlackboardAttribute> attributes = new ArrayList<BlackboardAttribute>();
4009 			while (rs.next()) {
4010 				int attributeTypeId = rs.getInt("attribute_type_id");
4011 				String attributeTypeName = rs.getString("type_name");
4012 				BlackboardAttribute.Type attributeType;
4013 				if (this.typeIdToAttributeTypeMap.containsKey(attributeTypeId)) {
4014 					attributeType = this.typeIdToAttributeTypeMap.get(attributeTypeId);
4015 				} else {
4016 					attributeType = new BlackboardAttribute.Type(attributeTypeId, attributeTypeName,
4017 							rs.getString("display_name"),
4018 							BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.fromType(rs.getInt("value_type")));
4019 					this.typeIdToAttributeTypeMap.put(attributeTypeId, attributeType);
4020 					this.typeNameToAttributeTypeMap.put(attributeTypeName, attributeType);
4021 				}
4022 
4023 				final BlackboardAttribute attr = new BlackboardAttribute(
4024 						rs.getLong("artifact_id"),
4025 						attributeType,
4026 						rs.getString("source"),
4027 						rs.getString("context"),
4028 						rs.getInt("value_int32"),
4029 						rs.getLong("value_int64"),
4030 						rs.getDouble("value_double"),
4031 						rs.getString("value_text"),
4032 						rs.getBytes("value_byte"), this
4033 				);
4034 				attributes.add(attr);
4035 			}
4036 			return attributes;
4037 		} catch (SQLException ex) {
4038 			throw new TskCoreException("Error getting attributes for artifact, artifact id = " + artifact.getArtifactID(), ex);
4039 		} finally {
4040 			closeResultSet(rs);
4041 			connection.close();
4042 			releaseSingleUserCaseReadLock();
4043 		}
4044 	}
4045 
4046 	/**
4047 	 * Get all attributes that match a where clause. The clause should begin
4048 	 * with "WHERE" or "JOIN". To use this method you must know the database
4049 	 * tables
4050 	 *
4051 	 * @param whereClause a sqlite where clause
4052 	 *
4053 	 * @return a list of matching attributes
4054 	 *
4055 	 * @throws TskCoreException exception thrown if a critical error occurs
4056 	 *                          within tsk core \ref query_database_page
4057 	 */
getMatchingAttributes(String whereClause)4058 	public ArrayList<BlackboardAttribute> getMatchingAttributes(String whereClause) throws TskCoreException {
4059 		CaseDbConnection connection = connections.getConnection();
4060 		acquireSingleUserCaseReadLock();
4061 		Statement s = null;
4062 		ResultSet rs = null;
4063 		try {
4064 			s = connection.createStatement();
4065 			rs = connection.executeQuery(s, "SELECT blackboard_attributes.artifact_id AS artifact_id, "
4066 					+ "blackboard_attributes.source AS source, blackboard_attributes.context AS context, "
4067 					+ "blackboard_attributes.attribute_type_id AS attribute_type_id, "
4068 					+ "blackboard_attributes.value_type AS value_type, blackboard_attributes.value_byte AS value_byte, "
4069 					+ "blackboard_attributes.value_text AS value_text, blackboard_attributes.value_int32 AS value_int32, "
4070 					+ "blackboard_attributes.value_int64 AS value_int64, blackboard_attributes.value_double AS value_double "
4071 					+ "FROM blackboard_attributes " + whereClause); //NON-NLS
4072 			ArrayList<BlackboardAttribute> matches = new ArrayList<BlackboardAttribute>();
4073 			while (rs.next()) {
4074 				BlackboardAttribute.Type type;
4075 				// attribute type is cached, so this does not necessarily call to the db
4076 				type = this.getAttributeType(rs.getInt("attribute_type_id"));
4077 				BlackboardAttribute attr = new BlackboardAttribute(
4078 						rs.getLong("artifact_id"),
4079 						type,
4080 						rs.getString("source"),
4081 						rs.getString("context"),
4082 						rs.getInt("value_int32"),
4083 						rs.getLong("value_int64"),
4084 						rs.getDouble("value_double"),
4085 						rs.getString("value_text"),
4086 						rs.getBytes("value_byte"), this
4087 				);
4088 				matches.add(attr);
4089 			}
4090 			return matches;
4091 		} catch (SQLException ex) {
4092 			throw new TskCoreException("Error getting attributes using this where clause: " + whereClause, ex);
4093 		} finally {
4094 			closeResultSet(rs);
4095 			closeStatement(s);
4096 			connection.close();
4097 			releaseSingleUserCaseReadLock();
4098 		}
4099 	}
4100 
4101 	/**
4102 	 * Get all artifacts that match a where clause. The clause should begin with
4103 	 * "WHERE" or "JOIN". To use this method you must know the database tables
4104 	 *
4105 	 * @param whereClause a sqlite where clause
4106 	 *
4107 	 * @return a list of matching artifacts
4108 	 *
4109 	 * @throws TskCoreException exception thrown if a critical error occurs
4110 	 *                          within tsk core \ref query_database_page
4111 	 */
getMatchingArtifacts(String whereClause)4112 	public ArrayList<BlackboardArtifact> getMatchingArtifacts(String whereClause) throws TskCoreException {
4113 		CaseDbConnection connection = connections.getConnection();
4114 		acquireSingleUserCaseReadLock();
4115 		ResultSet rs = null;
4116 		Statement s = null;
4117 		try {
4118 			s = connection.createStatement();
4119 			rs = connection.executeQuery(s, "SELECT blackboard_artifacts.artifact_id AS artifact_id, "
4120 					+ "blackboard_artifacts.obj_id AS obj_id, blackboard_artifacts.artifact_obj_id AS artifact_obj_id, blackboard_artifacts.data_source_obj_id AS data_source_obj_id, blackboard_artifacts.artifact_type_id AS artifact_type_id, "
4121 					+ "blackboard_artifacts.review_status_id AS review_status_id  "
4122 					+ "FROM blackboard_artifacts " + whereClause); //NON-NLS
4123 			ArrayList<BlackboardArtifact> matches = new ArrayList<BlackboardArtifact>();
4124 			while (rs.next()) {
4125 				BlackboardArtifact.Type type;
4126 				// artifact type is cached, so this does not necessarily call to the db
4127 				type = this.getArtifactType(rs.getInt("artifact_type_id"));
4128 				BlackboardArtifact artifact = new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
4129 						type.getTypeID(), type.getTypeName(), type.getDisplayName(),
4130 						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id")));
4131 				matches.add(artifact);
4132 			}
4133 			return matches;
4134 		} catch (SQLException ex) {
4135 			throw new TskCoreException("Error getting attributes using this where clause: " + whereClause, ex);
4136 		} finally {
4137 			closeResultSet(rs);
4138 			closeStatement(s);
4139 			connection.close();
4140 			releaseSingleUserCaseReadLock();
4141 		}
4142 	}
4143 
4144 	/**
4145 	 * Add a new blackboard artifact with the given type. If that artifact type
4146 	 * does not exist an error will be thrown. The artifact type name can be
4147 	 * looked up in the returned blackboard artifact.
4148 	 *
4149 	 * @param artifactTypeID the type the given artifact should have
4150 	 * @param obj_id         the content object id associated with this artifact
4151 	 *
4152 	 * @return a new blackboard artifact
4153 	 *
4154 	 * @throws TskCoreException exception thrown if a critical error occurs
4155 	 *                          within tsk core
4156 	 */
newBlackboardArtifact(int artifactTypeID, long obj_id)4157 	public BlackboardArtifact newBlackboardArtifact(int artifactTypeID, long obj_id) throws TskCoreException {
4158 		BlackboardArtifact.Type type = getArtifactType(artifactTypeID);
4159 		return newBlackboardArtifact(artifactTypeID, obj_id, type.getTypeName(), type.getDisplayName());
4160 	}
4161 
4162 	/**
4163 	 * Add a new blackboard artifact with the given type.
4164 	 *
4165 	 * @param artifactType the type the given artifact should have
4166 	 * @param obj_id       the content object id associated with this artifact
4167 	 *
4168 	 * @return a new blackboard artifact
4169 	 *
4170 	 * @throws TskCoreException exception thrown if a critical error occurs
4171 	 *                          within tsk core
4172 	 */
newBlackboardArtifact(ARTIFACT_TYPE artifactType, long obj_id)4173 	public BlackboardArtifact newBlackboardArtifact(ARTIFACT_TYPE artifactType, long obj_id) throws TskCoreException {
4174 		return newBlackboardArtifact(artifactType.getTypeID(), obj_id, artifactType.getLabel(), artifactType.getDisplayName());
4175 	}
4176 
newBlackboardArtifact(int artifact_type_id, long obj_id, String artifactTypeName, String artifactDisplayName)4177 	private BlackboardArtifact newBlackboardArtifact(int artifact_type_id, long obj_id, String artifactTypeName, String artifactDisplayName) throws TskCoreException {
4178 		CaseDbConnection connection = connections.getConnection();
4179 		acquireSingleUserCaseWriteLock();
4180 		ResultSet resultSet = null;
4181 		try {
4182 			long artifact_obj_id = addObject(obj_id, TskData.ObjectType.ARTIFACT.getObjectType(), connection);
4183 			long data_source_obj_id = getDataSourceObjectId(connection, obj_id);
4184 
4185 			PreparedStatement statement = null;
4186 			if (dbType == DbType.POSTGRESQL) {
4187 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.POSTGRESQL_INSERT_ARTIFACT, Statement.RETURN_GENERATED_KEYS);
4188 				statement.clearParameters();
4189 				statement.setLong(1, obj_id);
4190 				statement.setLong(2, artifact_obj_id);
4191 				statement.setLong(3, data_source_obj_id);
4192 				statement.setInt(4, artifact_type_id);
4193 
4194 			} else {
4195 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_ARTIFACT, Statement.RETURN_GENERATED_KEYS);
4196 				statement.clearParameters();
4197 				this.nextArtifactId++;
4198 				statement.setLong(1, this.nextArtifactId);
4199 				statement.setLong(2, obj_id);
4200 				statement.setLong(3, artifact_obj_id);
4201 				statement.setLong(4, data_source_obj_id);
4202 				statement.setInt(5, artifact_type_id);
4203 
4204 			}
4205 			connection.executeUpdate(statement);
4206 			resultSet = statement.getGeneratedKeys();
4207 			resultSet.next();
4208 			return new BlackboardArtifact(this, resultSet.getLong(1), //last_insert_rowid()
4209 					obj_id, artifact_obj_id, data_source_obj_id, artifact_type_id, artifactTypeName, artifactDisplayName, BlackboardArtifact.ReviewStatus.UNDECIDED, true);
4210 		} catch (SQLException ex) {
4211 			throw new TskCoreException("Error creating a blackboard artifact", ex);
4212 		} finally {
4213 			closeResultSet(resultSet);
4214 			connection.close();
4215 			releaseSingleUserCaseWriteLock();
4216 		}
4217 	}
4218 
4219 	/**
4220 	 * Checks if the content object has children. Note: this is generally more
4221 	 * efficient then preloading all children and checking if the set is empty,
4222 	 * and facilities lazy loading.
4223 	 *
4224 	 * @param content content object to check for children
4225 	 *
4226 	 * @return true if has children, false otherwise
4227 	 *
4228 	 * @throws TskCoreException exception thrown if a critical error occurs
4229 	 *                          within tsk core
4230 	 */
getContentHasChildren(Content content)4231 	boolean getContentHasChildren(Content content) throws TskCoreException {
4232 		CaseDbConnection connection = connections.getConnection();
4233 		acquireSingleUserCaseReadLock();
4234 		ResultSet rs = null;
4235 		try {
4236 			// SELECT COUNT(obj_id) AS count FROM tsk_objects WHERE par_obj_id = ?
4237 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_CHILD_OBJECTS_BY_PARENT);
4238 			statement.clearParameters();
4239 			statement.setLong(1, content.getId());
4240 			rs = connection.executeQuery(statement);
4241 			boolean hasChildren = false;
4242 			if (rs.next()) {
4243 				hasChildren = rs.getInt("count") > 0;
4244 			}
4245 			return hasChildren;
4246 		} catch (SQLException e) {
4247 			throw new TskCoreException("Error checking for children of parent " + content, e);
4248 		} finally {
4249 			closeResultSet(rs);
4250 			connection.close();
4251 			releaseSingleUserCaseReadLock();
4252 		}
4253 	}
4254 
4255 	/**
4256 	 * Counts if the content object children. Note: this is generally more
4257 	 * efficient then preloading all children and counting, and facilities lazy
4258 	 * loading.
4259 	 *
4260 	 * @param content content object to check for children count
4261 	 *
4262 	 * @return children count
4263 	 *
4264 	 * @throws TskCoreException exception thrown if a critical error occurs
4265 	 *                          within tsk core
4266 	 */
getContentChildrenCount(Content content)4267 	int getContentChildrenCount(Content content) throws TskCoreException {
4268 
4269 		if (!this.getHasChildren(content)) {
4270 			return 0;
4271 		}
4272 
4273 		CaseDbConnection connection = connections.getConnection();
4274 		acquireSingleUserCaseReadLock();
4275 		ResultSet rs = null;
4276 		try {
4277 			// SELECT COUNT(obj_id) AS count FROM tsk_objects WHERE par_obj_id = ?
4278 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_CHILD_OBJECTS_BY_PARENT);
4279 			statement.clearParameters();
4280 			statement.setLong(1, content.getId());
4281 			rs = connection.executeQuery(statement);
4282 			int countChildren = -1;
4283 			if (rs.next()) {
4284 				countChildren = rs.getInt("count");
4285 			}
4286 			return countChildren;
4287 		} catch (SQLException e) {
4288 			throw new TskCoreException("Error checking for children of parent " + content, e);
4289 		} finally {
4290 			closeResultSet(rs);
4291 			connection.close();
4292 			releaseSingleUserCaseReadLock();
4293 		}
4294 	}
4295 
4296 	/**
4297 	 * Returns the list of AbstractFile Children of a given type for a given
4298 	 * AbstractFileParent
4299 	 *
4300 	 * @param parent the content parent to get abstract file children for
4301 	 * @param type   children type to look for, defined in
4302 	 *               TSK_DB_FILES_TYPE_ENUM
4303 	 *
4304 	 * @throws TskCoreException exception thrown if a critical error occurs
4305 	 *                          within tsk core
4306 	 */
getAbstractFileChildren(Content parent, TSK_DB_FILES_TYPE_ENUM type)4307 	List<Content> getAbstractFileChildren(Content parent, TSK_DB_FILES_TYPE_ENUM type) throws TskCoreException {
4308 		CaseDbConnection connection = connections.getConnection();
4309 		acquireSingleUserCaseReadLock();
4310 		ResultSet rs = null;
4311 		try {
4312 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILES_BY_PARENT_AND_TYPE);
4313 			statement.clearParameters();
4314 			long parentId = parent.getId();
4315 			statement.setLong(1, parentId);
4316 			statement.setShort(2, type.getFileType());
4317 			rs = connection.executeQuery(statement);
4318 			return fileChildren(rs, connection, parentId);
4319 		} catch (SQLException ex) {
4320 			throw new TskCoreException("Error getting AbstractFile children for Content", ex);
4321 		} finally {
4322 			closeResultSet(rs);
4323 			connection.close();
4324 			releaseSingleUserCaseReadLock();
4325 		}
4326 	}
4327 
4328 	/**
4329 	 * Returns the list of all AbstractFile Children for a given
4330 	 * AbstractFileParent
4331 	 *
4332 	 * @param parent the content parent to get abstract file children for
4333 	 *
4334 	 * @throws TskCoreException exception thrown if a critical error occurs
4335 	 *                          within tsk core
4336 	 */
getAbstractFileChildren(Content parent)4337 	List<Content> getAbstractFileChildren(Content parent) throws TskCoreException {
4338 		CaseDbConnection connection = connections.getConnection();
4339 		acquireSingleUserCaseReadLock();
4340 		ResultSet rs = null;
4341 		try {
4342 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILES_BY_PARENT);
4343 			statement.clearParameters();
4344 			long parentId = parent.getId();
4345 			statement.setLong(1, parentId);
4346 			rs = connection.executeQuery(statement);
4347 			return fileChildren(rs, connection, parentId);
4348 		} catch (SQLException ex) {
4349 			throw new TskCoreException("Error getting AbstractFile children for Content", ex);
4350 		} finally {
4351 			closeResultSet(rs);
4352 			connection.close();
4353 			releaseSingleUserCaseReadLock();
4354 		}
4355 	}
4356 
4357 	/**
4358 	 * Get list of IDs for abstract files of a given type that are children of a
4359 	 * given content.
4360 	 *
4361 	 * @param parent Object to find children for
4362 	 * @param type   Type of children to find IDs for
4363 	 *
4364 	 * @return
4365 	 *
4366 	 * @throws TskCoreException
4367 	 */
getAbstractFileChildrenIds(Content parent, TSK_DB_FILES_TYPE_ENUM type)4368 	List<Long> getAbstractFileChildrenIds(Content parent, TSK_DB_FILES_TYPE_ENUM type) throws TskCoreException {
4369 		CaseDbConnection connection = connections.getConnection();
4370 		acquireSingleUserCaseReadLock();
4371 		ResultSet rs = null;
4372 		try {
4373 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_IDS_BY_PARENT_AND_TYPE);
4374 			statement.clearParameters();
4375 			statement.setLong(1, parent.getId());
4376 			statement.setShort(2, type.getFileType());
4377 			rs = connection.executeQuery(statement);
4378 			List<Long> children = new ArrayList<Long>();
4379 			while (rs.next()) {
4380 				children.add(rs.getLong("obj_id"));
4381 			}
4382 			return children;
4383 		} catch (SQLException ex) {
4384 			throw new TskCoreException("Error getting AbstractFile children for Content", ex);
4385 		} finally {
4386 			closeResultSet(rs);
4387 			connection.close();
4388 			releaseSingleUserCaseReadLock();
4389 		}
4390 	}
4391 
4392 	/**
4393 	 * Get list of IDs for abstract files that are children of a given content.
4394 	 *
4395 	 * @param parent Object to find children for
4396 	 *
4397 	 * @return
4398 	 *
4399 	 * @throws TskCoreException
4400 	 */
getAbstractFileChildrenIds(Content parent)4401 	List<Long> getAbstractFileChildrenIds(Content parent) throws TskCoreException {
4402 		CaseDbConnection connection = connections.getConnection();
4403 		acquireSingleUserCaseReadLock();
4404 		ResultSet rs = null;
4405 		try {
4406 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_IDS_BY_PARENT);
4407 			statement.clearParameters();
4408 			statement.setLong(1, parent.getId());
4409 			rs = connection.executeQuery(statement);
4410 			List<Long> children = new ArrayList<Long>();
4411 			while (rs.next()) {
4412 				children.add(rs.getLong("obj_id"));
4413 			}
4414 			return children;
4415 		} catch (SQLException ex) {
4416 			throw new TskCoreException("Error getting AbstractFile children for Content", ex);
4417 		} finally {
4418 			closeResultSet(rs);
4419 			connection.close();
4420 			releaseSingleUserCaseReadLock();
4421 		}
4422 	}
4423 
4424 	/**
4425 	 * Get list of object IDs for artifacts that are children of a given
4426 	 * content.
4427 	 *
4428 	 * @param parent Object to find children for
4429 	 *
4430 	 * @return
4431 	 *
4432 	 * @throws TskCoreException
4433 	 */
getBlackboardArtifactChildrenIds(Content parent)4434 	List<Long> getBlackboardArtifactChildrenIds(Content parent) throws TskCoreException {
4435 		CaseDbConnection connection = connections.getConnection();
4436 		acquireSingleUserCaseReadLock();
4437 		ResultSet rs = null;
4438 		try {
4439 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_OBJECTIDS_BY_PARENT);
4440 			statement.clearParameters();
4441 			statement.setLong(1, parent.getId());
4442 			rs = connection.executeQuery(statement);
4443 			List<Long> children = new ArrayList<Long>();
4444 			while (rs.next()) {
4445 				children.add(rs.getLong("obj_id"));
4446 			}
4447 			return children;
4448 		} catch (SQLException ex) {
4449 			throw new TskCoreException("Error getting children for BlackboardArtifact", ex);
4450 		} finally {
4451 			closeResultSet(rs);
4452 			connection.close();
4453 			releaseSingleUserCaseReadLock();
4454 		}
4455 	}
4456 
4457 	/**
4458 	 * Get list of artifacts that are children of a given content.
4459 	 *
4460 	 * @param parent Object to find children for
4461 	 *
4462 	 * @return
4463 	 *
4464 	 * @throws TskCoreException
4465 	 */
getBlackboardArtifactChildren(Content parent)4466 	List<Content> getBlackboardArtifactChildren(Content parent) throws TskCoreException {
4467 
4468 		long parentId = parent.getId();
4469 		ArrayList<BlackboardArtifact> artsArray = getArtifactsHelper("blackboard_artifacts.obj_id = " + parentId + ";");
4470 
4471 		List<Content> lc = new ArrayList<Content>();
4472 		lc.addAll(artsArray);
4473 		return lc;
4474 	}
4475 
4476 	/**
4477 	 * Get info about children of a given Content from the database.
4478 	 *
4479 	 * @param c Parent object to run query against
4480 	 *
4481 	 * @throws TskCoreException exception thrown if a critical error occurs
4482 	 *                          within tsk core
4483 	 */
getChildrenInfo(Content c)4484 	Collection<ObjectInfo> getChildrenInfo(Content c) throws TskCoreException {
4485 		CaseDbConnection connection = connections.getConnection();
4486 		acquireSingleUserCaseReadLock();
4487 		Statement s = null;
4488 		ResultSet rs = null;
4489 		try {
4490 			s = connection.createStatement();
4491 			rs = connection.executeQuery(s, "SELECT tsk_objects.obj_id AS obj_id, tsk_objects.type AS type " //NON-NLS
4492 					+ "FROM tsk_objects LEFT JOIN tsk_files " //NON-NLS
4493 					+ "ON tsk_objects.obj_id = tsk_files.obj_id " //NON-NLS
4494 					+ "WHERE tsk_objects.par_obj_id = " + c.getId()
4495 					+ " ORDER BY tsk_objects.obj_id"); //NON-NLS
4496 			Collection<ObjectInfo> infos = new ArrayList<ObjectInfo>();
4497 			while (rs.next()) {
4498 				infos.add(new ObjectInfo(rs.getLong("obj_id"), ObjectType.valueOf(rs.getShort("type")))); //NON-NLS
4499 			}
4500 			return infos;
4501 		} catch (SQLException ex) {
4502 			throw new TskCoreException("Error getting Children Info for Content", ex);
4503 		} finally {
4504 			closeResultSet(rs);
4505 			closeStatement(s);
4506 			connection.close();
4507 			releaseSingleUserCaseReadLock();
4508 		}
4509 	}
4510 
4511 	/**
4512 	 * Get parent info for the parent of the content object
4513 	 *
4514 	 * @param c content object to get parent info for
4515 	 *
4516 	 * @return the parent object info with the parent object type and id
4517 	 *
4518 	 * @throws TskCoreException exception thrown if a critical error occurs
4519 	 *                          within tsk core
4520 	 */
getParentInfo(Content c)4521 	ObjectInfo getParentInfo(Content c) throws TskCoreException {
4522 		return getParentInfo(c.getId());
4523 	}
4524 
4525 	/**
4526 	 * Get parent info for the parent of the content object id
4527 	 *
4528 	 * @param id content object id to get parent info for
4529 	 *
4530 	 * @return the parent object info with the parent object type and id
4531 	 *
4532 	 * @throws TskCoreException exception thrown if a critical error occurs
4533 	 *                          within tsk core
4534 	 */
getParentInfo(long contentId)4535 	ObjectInfo getParentInfo(long contentId) throws TskCoreException {
4536 		CaseDbConnection connection = connections.getConnection();
4537 		acquireSingleUserCaseReadLock();
4538 		Statement s = null;
4539 		ResultSet rs = null;
4540 		try {
4541 			s = connection.createStatement();
4542 			rs = connection.executeQuery(s, "SELECT parent.obj_id AS obj_id, parent.type AS type " //NON-NLS
4543 					+ "FROM tsk_objects AS parent INNER JOIN tsk_objects AS child " //NON-NLS
4544 					+ "ON child.par_obj_id = parent.obj_id " //NON-NLS
4545 					+ "WHERE child.obj_id = " + contentId); //NON-NLS
4546 			if (rs.next()) {
4547 				return new ObjectInfo(rs.getLong("obj_id"), ObjectType.valueOf(rs.getShort("type")));
4548 			} else {
4549 				return null;
4550 			}
4551 		} catch (SQLException ex) {
4552 			throw new TskCoreException("Error getting Parent Info for Content: " + contentId, ex);
4553 		} finally {
4554 			closeResultSet(rs);
4555 			closeStatement(s);
4556 			connection.close();
4557 			releaseSingleUserCaseReadLock();
4558 		}
4559 	}
4560 
4561 	/**
4562 	 * Gets parent directory for FsContent object
4563 	 *
4564 	 * @param fsc FsContent to get parent dir for
4565 	 *
4566 	 * @return the parent Directory or null if the Content has no parent
4567 	 *
4568 	 * @throws TskCoreException thrown if critical error occurred within tsk
4569 	 *                          core
4570 	 */
getParentDirectory(FsContent fsc)4571 	Directory getParentDirectory(FsContent fsc) throws TskCoreException {
4572 		if (fsc.isRoot()) {
4573 			// Given FsContent is a root object and can't have parent directory
4574 			return null;
4575 		} else {
4576 			ObjectInfo parentInfo = getParentInfo(fsc);
4577 			if (parentInfo == null) {
4578 				return null;
4579 			}
4580 			Directory parent = null;
4581 			if (parentInfo.type == ObjectType.ABSTRACTFILE) {
4582 				parent = getDirectoryById(parentInfo.id, fsc.getFileSystem());
4583 			} else {
4584 				throw new TskCoreException("Parent of FsContent (id: " + fsc.getId() + ") has wrong type to be directory: " + parentInfo.type);
4585 			}
4586 			return parent;
4587 		}
4588 	}
4589 
4590 	/**
4591 	 * Get content object by content id
4592 	 *
4593 	 * @param id to get content object for
4594 	 *
4595 	 * @return instance of a Content object (one of its subclasses), or null if
4596 	 *         not found.
4597 	 *
4598 	 * @throws TskCoreException thrown if critical error occurred within tsk
4599 	 *                          core
4600 	 */
getContentById(long id)4601 	public Content getContentById(long id) throws TskCoreException {
4602 		// First check to see if this exists in our frequently used content cache.
4603 		Content content = frequentlyUsedContentMap.get(id);
4604 		if (null != content) {
4605 			return content;
4606 		}
4607 
4608 		CaseDbConnection connection = connections.getConnection();
4609 		acquireSingleUserCaseReadLock();
4610 		Statement s = null;
4611 		ResultSet rs = null;
4612 		long parentId;
4613 		TskData.ObjectType type;
4614 
4615 		try {
4616 			s = connection.createStatement();
4617 			rs = connection.executeQuery(s, "SELECT * FROM tsk_objects WHERE obj_id = " + id + " LIMIT  1"); //NON-NLS
4618 			if (!rs.next()) {
4619 				return null;
4620 			}
4621 			parentId = rs.getLong("par_obj_id"); //NON-NLS
4622 			type = TskData.ObjectType.valueOf(rs.getShort("type")); //NON-NLS
4623 		} catch (SQLException ex) {
4624 			throw new TskCoreException("Error getting Content by ID.", ex);
4625 		} finally {
4626 			closeResultSet(rs);
4627 			closeStatement(s);
4628 			connection.close();
4629 			releaseSingleUserCaseReadLock();
4630 		}
4631 
4632 		// Construct the object
4633 		switch (type) {
4634 			case IMG:
4635 				content = getImageById(id);
4636 				frequentlyUsedContentMap.put(id, content);
4637 				break;
4638 			case VS:
4639 				content = getVolumeSystemById(id, parentId);
4640 				break;
4641 			case VOL:
4642 				content = getVolumeById(id, parentId);
4643 				frequentlyUsedContentMap.put(id, content);
4644 				break;
4645 			case FS:
4646 				content = getFileSystemById(id, parentId);
4647 				frequentlyUsedContentMap.put(id, content);
4648 				break;
4649 			case ABSTRACTFILE:
4650 				content = getAbstractFileById(id);
4651 
4652 				// Add virtual and root directories to frequently used map.
4653 				// Calling isRoot() on local directories goes up the entire directory structure
4654 				// and they can only be the root of portable cases, so skip trying to add
4655 				// them to the cache.
4656 				if (((AbstractFile) content).isVirtual()
4657 						|| ((!(content instanceof LocalDirectory)) && ((AbstractFile) content).isRoot())) {
4658 					frequentlyUsedContentMap.put(id, content);
4659 				}
4660 				break;
4661 			case ARTIFACT:
4662 				content = getArtifactById(id);
4663 				break;
4664 			case REPORT:
4665 				content = getReportById(id);
4666 				break;
4667 			default:
4668 				throw new TskCoreException("Could not obtain Content object with ID: " + id);
4669 		}
4670 
4671 		return content;
4672 	}
4673 
4674 	/**
4675 	 * Get a path of a file in tsk_files_path table or null if there is none
4676 	 *
4677 	 * @param id id of the file to get path for
4678 	 *
4679 	 * @return file path or null
4680 	 */
getFilePath(long id)4681 	String getFilePath(long id) {
4682 		CaseDbConnection connection;
4683 		try {
4684 			connection = connections.getConnection();
4685 		} catch (TskCoreException ex) {
4686 			logger.log(Level.SEVERE, "Error getting file path for file " + id, ex); //NON-NLS
4687 			return null;
4688 		}
4689 		String filePath = null;
4690 		acquireSingleUserCaseReadLock();
4691 		ResultSet rs = null;
4692 		try {
4693 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_LOCAL_PATH_FOR_FILE);
4694 			statement.clearParameters();
4695 			statement.setLong(1, id);
4696 			rs = connection.executeQuery(statement);
4697 			if (rs.next()) {
4698 				filePath = rs.getString("path");
4699 			}
4700 		} catch (SQLException ex) {
4701 			logger.log(Level.SEVERE, "Error getting file path for file " + id, ex); //NON-NLS
4702 		} finally {
4703 			closeResultSet(rs);
4704 			connection.close();
4705 			releaseSingleUserCaseReadLock();
4706 		}
4707 		return filePath;
4708 	}
4709 
4710 	/**
4711 	 * Get the encoding type for a file in tsk_files_path table
4712 	 *
4713 	 * @param id id of the file to get path for
4714 	 *
4715 	 * @return Encoding type (NONE if nothing was found)
4716 	 */
getEncodingType(long id)4717 	TskData.EncodingType getEncodingType(long id) {
4718 		CaseDbConnection connection;
4719 		try {
4720 			connection = connections.getConnection();
4721 		} catch (TskCoreException ex) {
4722 			logger.log(Level.SEVERE, "Error getting file path for file " + id, ex); //NON-NLS
4723 			return null;
4724 		}
4725 		TskData.EncodingType type = TskData.EncodingType.NONE;
4726 		acquireSingleUserCaseReadLock();
4727 		ResultSet rs = null;
4728 		try {
4729 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ENCODING_FOR_FILE);
4730 			statement.clearParameters();
4731 			statement.setLong(1, id);
4732 			rs = connection.executeQuery(statement);
4733 			if (rs.next()) {
4734 				type = TskData.EncodingType.valueOf(rs.getInt(1));
4735 			}
4736 		} catch (SQLException ex) {
4737 			logger.log(Level.SEVERE, "Error getting encoding type for file " + id, ex); //NON-NLS
4738 		} finally {
4739 			closeResultSet(rs);
4740 			connection.close();
4741 			releaseSingleUserCaseReadLock();
4742 		}
4743 		return type;
4744 	}
4745 
4746 	/**
4747 	 * Gets the parent_path of a file.
4748 	 *
4749 	 * @param objectId   The object id of the file.
4750 	 * @param connection An open database connection.
4751 	 *
4752 	 * @return The path of the file or null.
4753 	 */
getFileParentPath(long objectId, CaseDbConnection connection)4754 	String getFileParentPath(long objectId, CaseDbConnection connection) {
4755 		String parentPath = null;
4756 		acquireSingleUserCaseReadLock();
4757 		ResultSet rs = null;
4758 		try {
4759 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_PATH_FOR_FILE);
4760 			statement.clearParameters();
4761 			statement.setLong(1, objectId);
4762 			rs = connection.executeQuery(statement);
4763 			if (rs.next()) {
4764 				parentPath = rs.getString("parent_path");
4765 			}
4766 		} catch (SQLException ex) {
4767 			logger.log(Level.SEVERE, "Error getting file parent_path for file " + objectId, ex); //NON-NLS
4768 		} finally {
4769 			closeResultSet(rs);
4770 			releaseSingleUserCaseReadLock();
4771 		}
4772 		return parentPath;
4773 	}
4774 
4775 	/**
4776 	 * Gets the name of a file.
4777 	 *
4778 	 * @param objectId   The object id of the file.
4779 	 * @param connection An open database connection.
4780 	 *
4781 	 * @return The path of the file or null.
4782 	 */
getFileName(long objectId, CaseDbConnection connection)4783 	String getFileName(long objectId, CaseDbConnection connection) {
4784 		String fileName = null;
4785 		acquireSingleUserCaseReadLock();
4786 		ResultSet rs = null;
4787 		try {
4788 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_NAME);
4789 			statement.clearParameters();
4790 			statement.setLong(1, objectId);
4791 			rs = connection.executeQuery(statement);
4792 			if (rs.next()) {
4793 				fileName = rs.getString("name");
4794 			}
4795 		} catch (SQLException ex) {
4796 			logger.log(Level.SEVERE, "Error getting file parent_path for file " + objectId, ex); //NON-NLS
4797 		} finally {
4798 			closeResultSet(rs);
4799 			releaseSingleUserCaseReadLock();
4800 		}
4801 		return fileName;
4802 	}
4803 
4804 	/**
4805 	 * Get a derived method for a file, or null if none
4806 	 *
4807 	 * @param id id of the derived file
4808 	 *
4809 	 * @return derived method or null if not present
4810 	 *
4811 	 * @throws TskCoreException exception throws if core error occurred and
4812 	 *                          method could not be queried
4813 	 */
getDerivedMethod(long id)4814 	DerivedFile.DerivedMethod getDerivedMethod(long id) throws TskCoreException {
4815 		CaseDbConnection connection = connections.getConnection();
4816 		DerivedFile.DerivedMethod method = null;
4817 		acquireSingleUserCaseReadLock();
4818 		ResultSet rs1 = null;
4819 		ResultSet rs2 = null;
4820 		try {
4821 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_DERIVED_FILE);
4822 			statement.clearParameters();
4823 			statement.setLong(1, id);
4824 			rs1 = connection.executeQuery(statement);
4825 			if (rs1.next()) {
4826 				int method_id = rs1.getInt("derived_id");
4827 				String rederive = rs1.getString("rederive");
4828 				method = new DerivedFile.DerivedMethod(method_id, rederive);
4829 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_DERIVATION_METHOD);
4830 				statement.clearParameters();
4831 				statement.setInt(1, method_id);
4832 				rs2 = connection.executeQuery(statement);
4833 				if (rs2.next()) {
4834 					method.setToolName(rs2.getString("tool_name"));
4835 					method.setToolVersion(rs2.getString("tool_version"));
4836 					method.setOther(rs2.getString("other"));
4837 				}
4838 			}
4839 		} catch (SQLException e) {
4840 			logger.log(Level.SEVERE, "Error getting derived method for file: " + id, e); //NON-NLS
4841 		} finally {
4842 			closeResultSet(rs2);
4843 			closeResultSet(rs1);
4844 			connection.close();
4845 			releaseSingleUserCaseReadLock();
4846 		}
4847 		return method;
4848 	}
4849 
4850 	/**
4851 	 * Get abstract file object from tsk_files table by its id
4852 	 *
4853 	 * @param id id of the file object in tsk_files table
4854 	 *
4855 	 * @return AbstractFile object populated, or null if not found.
4856 	 *
4857 	 * @throws TskCoreException thrown if critical error occurred within tsk
4858 	 *                          core and file could not be queried
4859 	 */
getAbstractFileById(long id)4860 	public AbstractFile getAbstractFileById(long id) throws TskCoreException {
4861 		CaseDbConnection connection = connections.getConnection();
4862 		try {
4863 			return getAbstractFileById(id, connection);
4864 		} finally {
4865 			connection.close();
4866 		}
4867 	}
4868 
4869 	/**
4870 	 * Get abstract file object from tsk_files table by its id on an existing
4871 	 * connection.
4872 	 *
4873 	 * @param objectId   The id of the file object in tsk_files table.
4874 	 * @param connection An open database connection.
4875 	 *
4876 	 * @return AbstractFile object populated, or null if not found.
4877 	 *
4878 	 * @throws TskCoreException thrown if critical error occurred within tsk
4879 	 *                          core and file could not be queried
4880 	 */
getAbstractFileById(long objectId, CaseDbConnection connection)4881 	AbstractFile getAbstractFileById(long objectId, CaseDbConnection connection) throws TskCoreException {
4882 		acquireSingleUserCaseReadLock();
4883 		ResultSet rs = null;
4884 		try {
4885 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_BY_ID);
4886 			statement.clearParameters();
4887 			statement.setLong(1, objectId);
4888 			rs = connection.executeQuery(statement);
4889 			List<AbstractFile> files = resultSetToAbstractFiles(rs, connection);
4890 			if (files.size() > 0) {
4891 				return files.get(0);
4892 			} else {
4893 				return null;
4894 			}
4895 		} catch (SQLException ex) {
4896 			throw new TskCoreException("Error getting file by id, id = " + objectId, ex);
4897 		} finally {
4898 			closeResultSet(rs);
4899 			releaseSingleUserCaseReadLock();
4900 		}
4901 	}
4902 
4903 	/**
4904 	 * Get artifact from blackboard_artifacts table by its artifact_obj_id
4905 	 *
4906 	 * @param id id of the artifact in blackboard_artifacts table
4907 	 *
4908 	 * @return Artifact object populated, or null if not found.
4909 	 *
4910 	 * @throws TskCoreException thrown if critical error occurred within tsk
4911 	 *                          core and file could not be queried
4912 	 */
getArtifactById(long id)4913 	public BlackboardArtifact getArtifactById(long id) throws TskCoreException {
4914 		CaseDbConnection connection = connections.getConnection();
4915 		acquireSingleUserCaseReadLock();
4916 		ResultSet rs = null;
4917 		try {
4918 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_BY_ARTIFACT_OBJ_ID);
4919 			statement.clearParameters();
4920 			statement.setLong(1, id);
4921 			rs = connection.executeQuery(statement);
4922 			List<BlackboardArtifact> artifacts = resultSetToArtifacts(rs);
4923 			if (artifacts.size() > 0) {
4924 				return artifacts.get(0);
4925 			} else {
4926 				return null;
4927 			}
4928 		} catch (SQLException ex) {
4929 			throw new TskCoreException("Error getting artifacts by artifact_obj_id, artifact_obj_id = " + id, ex);
4930 		} finally {
4931 			closeResultSet(rs);
4932 			connection.close();
4933 			releaseSingleUserCaseReadLock();
4934 		}
4935 	}
4936 
4937 	/**
4938 	 * Get artifact from blackboard_artifacts table by its artifact_id
4939 	 *
4940 	 * @param id Artifact ID of the artifact in blackboard_artifacts table
4941 	 *
4942 	 * @return Artifact object populated, or null if not found.
4943 	 *
4944 	 * @throws TskCoreException thrown if critical error occurred within tsk
4945 	 *                          core and file could not be queried
4946 	 */
getArtifactByArtifactId(long id)4947 	public BlackboardArtifact getArtifactByArtifactId(long id) throws TskCoreException {
4948 		CaseDbConnection connection = connections.getConnection();
4949 		acquireSingleUserCaseReadLock();
4950 		ResultSet rs = null;
4951 		try {
4952 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_BY_ARTIFACT_ID);
4953 			statement.clearParameters();
4954 			statement.setLong(1, id);
4955 			rs = connection.executeQuery(statement);
4956 			List<BlackboardArtifact> artifacts = resultSetToArtifacts(rs);
4957 			if (artifacts.size() > 0) {
4958 				return artifacts.get(0);
4959 			} else {
4960 				return null;
4961 			}
4962 		} catch (SQLException ex) {
4963 			throw new TskCoreException("Error getting artifacts by artifact id, artifact id = " + id, ex);
4964 		} finally {
4965 			closeResultSet(rs);
4966 			connection.close();
4967 			releaseSingleUserCaseReadLock();
4968 		}
4969 	}
4970 
4971 	/**
4972 	 * Get the object ID of the file system that a file is located in.
4973 	 *
4974 	 * Note: for FsContent files, this is the real fs for other non-fs
4975 	 * AbstractFile files, this field is used internally for data source id (the
4976 	 * root content obj)
4977 	 *
4978 	 * @param fileId     object id of the file to get fs column id for
4979 	 * @param connection the database connection to use
4980 	 *
4981 	 * @return fs_id or -1 if not present
4982 	 */
getFileSystemId(long fileId, CaseDbConnection connection)4983 	private long getFileSystemId(long fileId, CaseDbConnection connection) {
4984 		acquireSingleUserCaseReadLock();
4985 		ResultSet rs = null;
4986 		long ret = -1;
4987 		try {
4988 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILE_SYSTEM_BY_OBJECT);
4989 			statement.clearParameters();
4990 			statement.setLong(1, fileId);
4991 			rs = connection.executeQuery(statement);
4992 			if (rs.next()) {
4993 				ret = rs.getLong("fs_obj_id");
4994 				if (ret == 0) {
4995 					ret = -1;
4996 				}
4997 			}
4998 		} catch (SQLException e) {
4999 			logger.log(Level.SEVERE, "Error checking file system id of a file, id = " + fileId, e); //NON-NLS
5000 		} finally {
5001 			closeResultSet(rs);
5002 			releaseSingleUserCaseReadLock();
5003 		}
5004 		return ret;
5005 	}
5006 
5007 	/**
5008 	 * Checks if the file is a (sub)child of the data source (parentless Content
5009 	 * object such as Image or VirtualDirectory representing filesets)
5010 	 *
5011 	 * @param dataSource dataSource to check
5012 	 * @param fileId     id of file to check
5013 	 *
5014 	 * @return true if the file is in the dataSource hierarchy
5015 	 *
5016 	 * @throws TskCoreException thrown if check failed
5017 	 */
isFileFromSource(Content dataSource, long fileId)5018 	public boolean isFileFromSource(Content dataSource, long fileId) throws TskCoreException {
5019 		String query = String.format("SELECT COUNT(*) AS count FROM tsk_files WHERE obj_id = %d AND data_source_obj_id = %d", fileId, dataSource.getId()); //NON-NLS
5020 		CaseDbConnection connection = connections.getConnection();
5021 		acquireSingleUserCaseReadLock();
5022 		Statement statement = null;
5023 		ResultSet resultSet = null;
5024 		try {
5025 			statement = connection.createStatement();
5026 			resultSet = connection.executeQuery(statement, query);
5027 			resultSet.next();
5028 			return (resultSet.getLong("count") > 0L);
5029 		} catch (SQLException ex) {
5030 			throw new TskCoreException(String.format("Error executing query %s", query), ex);
5031 		} finally {
5032 			closeResultSet(resultSet);
5033 			closeStatement(statement);
5034 			connection.close();
5035 			releaseSingleUserCaseReadLock();
5036 		}
5037 	}
5038 
5039 	/**
5040 	 * @param dataSource the dataSource (Image, parent-less VirtualDirectory) to
5041 	 *                   search for the given file name
5042 	 * @param fileName   Pattern of the name of the file or directory to match
5043 	 *                   (case insensitive, used in LIKE SQL statement).
5044 	 *
5045 	 * @return a list of AbstractFile for files/directories whose name matches
5046 	 *         the given fileName
5047 	 *
5048 	 * @throws TskCoreException thrown if check failed
5049 	 */
findFiles(Content dataSource, String fileName)5050 	public List<AbstractFile> findFiles(Content dataSource, String fileName) throws TskCoreException {
5051 		List<AbstractFile> files = new ArrayList<AbstractFile>();
5052 		CaseDbConnection connection = connections.getConnection();
5053 		acquireSingleUserCaseReadLock();
5054 		ResultSet resultSet = null;
5055 		try {
5056 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILES_BY_DATA_SOURCE_AND_NAME);
5057 			statement.clearParameters();
5058 			statement.setString(1, fileName.toLowerCase());
5059 			statement.setLong(2, dataSource.getId());
5060 			resultSet = connection.executeQuery(statement);
5061 			files.addAll(resultSetToAbstractFiles(resultSet, connection));
5062 		} catch (SQLException e) {
5063 			throw new TskCoreException(bundle.getString("SleuthkitCase.findFiles.exception.msg3.text"), e);
5064 		} finally {
5065 			closeResultSet(resultSet);
5066 			connection.close();
5067 			releaseSingleUserCaseReadLock();
5068 		}
5069 		return files;
5070 	}
5071 
5072 	/**
5073 	 * @param dataSource   the dataSource (Image, parent-less VirtualDirectory)
5074 	 *                     to search for the given file name
5075 	 * @param fileName     Pattern of the name of the file or directory to match
5076 	 *                     (case insensitive, used in LIKE SQL statement).
5077 	 * @param dirSubString Substring that must exist in parent path. Will be
5078 	 *                     surrounded by % in LIKE query
5079 	 *
5080 	 * @return a list of AbstractFile for files/directories whose name matches
5081 	 *         fileName and whose parent directory contains dirName.
5082 	 *
5083 	 * @throws org.sleuthkit.datamodel.TskCoreException
5084 	 */
findFiles(Content dataSource, String fileName, String dirSubString)5085 	public List<AbstractFile> findFiles(Content dataSource, String fileName, String dirSubString) throws TskCoreException {
5086 		List<AbstractFile> files = new ArrayList<AbstractFile>();
5087 		CaseDbConnection connection = connections.getConnection();
5088 		acquireSingleUserCaseReadLock();
5089 		ResultSet resultSet = null;
5090 		try {
5091 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_FILES_BY_DATA_SOURCE_AND_PARENT_PATH_AND_NAME);
5092 			statement.clearParameters();
5093 			statement.setString(1, fileName.toLowerCase());
5094 			statement.setString(2, "%" + dirSubString.toLowerCase() + "%"); //NON-NLS
5095 			statement.setLong(3, dataSource.getId());
5096 			resultSet = connection.executeQuery(statement);
5097 			files.addAll(resultSetToAbstractFiles(resultSet, connection));
5098 		} catch (SQLException e) {
5099 			throw new TskCoreException(bundle.getString("SleuthkitCase.findFiles3.exception.msg3.text"), e);
5100 		} finally {
5101 			closeResultSet(resultSet);
5102 			connection.close();
5103 			releaseSingleUserCaseReadLock();
5104 		}
5105 		return files;
5106 	}
5107 
5108 	/**
5109 	 * Adds a virtual directory to the database and returns a VirtualDirectory
5110 	 * object representing it.
5111 	 *
5112 	 * @param parentId      the ID of the parent, or 0 if NULL
5113 	 * @param directoryName the name of the virtual directory to create
5114 	 *
5115 	 * @return
5116 	 *
5117 	 * @throws TskCoreException
5118 	 */
addVirtualDirectory(long parentId, String directoryName)5119 	public VirtualDirectory addVirtualDirectory(long parentId, String directoryName) throws TskCoreException {
5120 		CaseDbTransaction localTrans = beginTransaction();
5121 		localTrans.acquireSingleUserCaseWriteLock();
5122 		try {
5123 			VirtualDirectory newVD = addVirtualDirectory(parentId, directoryName, localTrans);
5124 			localTrans.commit();
5125 			localTrans = null;
5126 			return newVD;
5127 		} finally {
5128 			// NOTE: write lock will be released by transaction
5129 			if (null != localTrans) {
5130 				try {
5131 					localTrans.rollback();
5132 				} catch (TskCoreException ex2) {
5133 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
5134 				}
5135 			}
5136 		}
5137 	}
5138 
5139 	/**
5140 	 * Add an object to the tsk_objects table. Returns the object ID for the new
5141 	 * object.
5142 	 *
5143 	 * @param parentId   Parent of the new object
5144 	 * @param objectType Type of the new object
5145 	 * @param connection Case connection
5146 	 *
5147 	 * @return the object ID for the new object
5148 	 *
5149 	 * @throws SQLException
5150 	 */
addObject(long parentId, int objectType, CaseDbConnection connection)5151 	private long addObject(long parentId, int objectType, CaseDbConnection connection) throws SQLException {
5152 		ResultSet resultSet = null;
5153 		acquireSingleUserCaseWriteLock();
5154 		try {
5155 			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
5156 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_OBJECT, Statement.RETURN_GENERATED_KEYS);
5157 			statement.clearParameters();
5158 			if (parentId != 0) {
5159 				statement.setLong(1, parentId);
5160 			} else {
5161 				statement.setNull(1, java.sql.Types.BIGINT);
5162 			}
5163 			statement.setInt(2, objectType);
5164 			connection.executeUpdate(statement);
5165 			resultSet = statement.getGeneratedKeys();
5166 
5167 			if (resultSet.next()) {
5168 				if (parentId != 0) {
5169 					setHasChildren(parentId);
5170 				}
5171 				return resultSet.getLong(1); //last_insert_rowid()
5172 			} else {
5173 				throw new SQLException("Error inserting object with parent " + parentId + " into tsk_objects");
5174 			}
5175 		} finally {
5176 			closeResultSet(resultSet);
5177 			releaseSingleUserCaseWriteLock();
5178 		}
5179 	}
5180 
5181 	/**
5182 	 * Adds a virtual directory to the database and returns a VirtualDirectory
5183 	 * object representing it.
5184 	 *
5185 	 * Make sure the connection in transaction is used for all database
5186 	 * interactions called by this method
5187 	 *
5188 	 * @param parentId      the ID of the parent, or 0 if NULL
5189 	 * @param directoryName the name of the virtual directory to create
5190 	 * @param transaction   the transaction in the scope of which the operation
5191 	 *                      is to be performed, managed by the caller
5192 	 *
5193 	 * @return a VirtualDirectory object representing the one added to the
5194 	 *         database.
5195 	 *
5196 	 * @throws TskCoreException
5197 	 */
addVirtualDirectory(long parentId, String directoryName, CaseDbTransaction transaction)5198 	public VirtualDirectory addVirtualDirectory(long parentId, String directoryName, CaseDbTransaction transaction) throws TskCoreException {
5199 		if (transaction == null) {
5200 			throw new TskCoreException("Passed null CaseDbTransaction");
5201 		}
5202 
5203 		transaction.acquireSingleUserCaseWriteLock();
5204 		ResultSet resultSet = null;
5205 		try {
5206 			// Get the parent path.
5207 			CaseDbConnection connection = transaction.getConnection();
5208 
5209 			String parentPath;
5210 			Content parent = this.getAbstractFileById(parentId, connection);
5211 			if (parent instanceof AbstractFile) {
5212 				if (isRootDirectory((AbstractFile) parent, transaction)) {
5213 					parentPath = "/";
5214 				} else {
5215 					parentPath = ((AbstractFile) parent).getParentPath() + parent.getName() + "/"; //NON-NLS
5216 				}
5217 			} else {
5218 				// The parent was either null or not an abstract file
5219 				parentPath = "/";
5220 			}
5221 
5222 			// Insert a row for the virtual directory into the tsk_objects table.
5223 			long newObjId = addObject(parentId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
5224 
5225 			// Insert a row for the virtual directory into the tsk_files table.
5226 			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
5227 			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension)
5228 			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
5229 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
5230 			statement.clearParameters();
5231 			statement.setLong(1, newObjId);
5232 
5233 			// If the parent is part of a file system, grab its file system ID
5234 			if (0 != parentId) {
5235 				long parentFs = this.getFileSystemId(parentId, connection);
5236 				if (parentFs != -1) {
5237 					statement.setLong(2, parentFs);
5238 				} else {
5239 					statement.setNull(2, java.sql.Types.BIGINT);
5240 				}
5241 			} else {
5242 				statement.setNull(2, java.sql.Types.BIGINT);
5243 			}
5244 
5245 			// name
5246 			statement.setString(3, directoryName);
5247 
5248 			//type
5249 			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType());
5250 			statement.setShort(5, (short) 1);
5251 
5252 			//flags
5253 			final TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
5254 			statement.setShort(6, dirType.getValue());
5255 			final TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
5256 			statement.setShort(7, metaType.getValue());
5257 
5258 			//allocated
5259 			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
5260 			statement.setShort(8, dirFlag.getValue());
5261 			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
5262 					| TSK_FS_META_FLAG_ENUM.USED.getValue());
5263 			statement.setShort(9, metaFlags);
5264 
5265 			//size
5266 			statement.setLong(10, 0);
5267 
5268 			//  nulls for params 11-14
5269 			statement.setNull(11, java.sql.Types.BIGINT);
5270 			statement.setNull(12, java.sql.Types.BIGINT);
5271 			statement.setNull(13, java.sql.Types.BIGINT);
5272 			statement.setNull(14, java.sql.Types.BIGINT);
5273 
5274 			statement.setNull(15, java.sql.Types.VARCHAR); // MD5
5275 			statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
5276 			statement.setNull(17, java.sql.Types.VARCHAR); // MIME type
5277 
5278 			// parent path
5279 			statement.setString(18, parentPath);
5280 
5281 			// data source object id (same as object id if this is a data source)
5282 			long dataSourceObjectId;
5283 			if (0 == parentId) {
5284 				dataSourceObjectId = newObjId;
5285 			} else {
5286 				dataSourceObjectId = getDataSourceObjectId(connection, parentId);
5287 			}
5288 			statement.setLong(19, dataSourceObjectId);
5289 
5290 			//extension, since this is not really file we just set it to null
5291 			statement.setString(20, null);
5292 			connection.executeUpdate(statement);
5293 
5294 			return new VirtualDirectory(this, newObjId, dataSourceObjectId, directoryName, dirType,
5295 					metaType, dirFlag, metaFlags, null, FileKnown.UNKNOWN,
5296 					parentPath);
5297 		} catch (SQLException e) {
5298 			throw new TskCoreException("Error creating virtual directory '" + directoryName + "'", e);
5299 		} finally {
5300 			closeResultSet(resultSet);
5301 			// NOTE: write lock will be released by transaction
5302 		}
5303 	}
5304 
5305 	/**
5306 	 * Adds a local directory to the database and returns a LocalDirectory
5307 	 * object representing it.
5308 	 *
5309 	 * @param parentId      the ID of the parent, or 0 if NULL
5310 	 * @param directoryName the name of the local directory to create
5311 	 *
5312 	 * @return a LocalDirectory object representing the one added to the
5313 	 *         database.
5314 	 *
5315 	 * @throws TskCoreException
5316 	 */
addLocalDirectory(long parentId, String directoryName)5317 	public LocalDirectory addLocalDirectory(long parentId, String directoryName) throws TskCoreException {
5318 		acquireSingleUserCaseWriteLock();
5319 		CaseDbTransaction localTrans = beginTransaction();
5320 		try {
5321 			LocalDirectory newLD = addLocalDirectory(parentId, directoryName, localTrans);
5322 			localTrans.commit();
5323 			return newLD;
5324 		} catch (TskCoreException ex) {
5325 			try {
5326 				localTrans.rollback();
5327 			} catch (TskCoreException ex2) {
5328 				logger.log(Level.SEVERE, String.format("Failed to rollback transaction after exception: %s", ex.getMessage()), ex2);
5329 			}
5330 			throw ex;
5331 		} finally {
5332 			releaseSingleUserCaseWriteLock();
5333 		}
5334 	}
5335 
5336 	/**
5337 	 * Adds a local directory to the database and returns a LocalDirectory
5338 	 * object representing it.
5339 	 *
5340 	 * Make sure the connection in transaction is used for all database
5341 	 * interactions called by this method
5342 	 *
5343 	 * @param parentId      the ID of the parent, or 0 if NULL
5344 	 * @param directoryName the name of the local directory to create
5345 	 * @param transaction   the transaction in the scope of which the operation
5346 	 *                      is to be performed, managed by the caller
5347 	 *
5348 	 * @return a LocalDirectory object representing the one added to the
5349 	 *         database.
5350 	 *
5351 	 * @throws TskCoreException
5352 	 */
addLocalDirectory(long parentId, String directoryName, CaseDbTransaction transaction)5353 	public LocalDirectory addLocalDirectory(long parentId, String directoryName, CaseDbTransaction transaction) throws TskCoreException {
5354 		if (transaction == null) {
5355 			throw new TskCoreException("Passed null CaseDbTransaction");
5356 		}
5357 
5358 		transaction.acquireSingleUserCaseWriteLock();
5359 		ResultSet resultSet = null;
5360 		try {
5361 			// Get the parent path.
5362 			CaseDbConnection connection = transaction.getConnection();
5363 			AbstractFile parent = getAbstractFileById(parentId, connection);
5364 			String parentPath;
5365 			if ((parent == null) || isRootDirectory(parent, transaction)) {
5366 				parentPath = "/";
5367 			} else {
5368 				parentPath = parent.getParentPath() + parent.getName() + "/"; //NON-NLS
5369 			}
5370 
5371 			// Insert a row for the local directory into the tsk_objects table.
5372 			long newObjId = addObject(parentId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
5373 
5374 			// Insert a row for the local directory into the tsk_files table.
5375 			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
5376 			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id)
5377 			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5378 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
5379 			statement.clearParameters();
5380 			statement.setLong(1, newObjId);
5381 
5382 			// The parent of a local directory will never be a file system
5383 			statement.setNull(2, java.sql.Types.BIGINT);
5384 
5385 			// name
5386 			statement.setString(3, directoryName);
5387 
5388 			//type
5389 			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR.getFileType());
5390 			statement.setShort(5, (short) 1);
5391 
5392 			//flags
5393 			final TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
5394 			statement.setShort(6, dirType.getValue());
5395 			final TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
5396 			statement.setShort(7, metaType.getValue());
5397 
5398 			//allocated
5399 			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
5400 			statement.setShort(8, dirFlag.getValue());
5401 			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
5402 					| TSK_FS_META_FLAG_ENUM.USED.getValue());
5403 			statement.setShort(9, metaFlags);
5404 
5405 			//size
5406 			statement.setLong(10, 0);
5407 
5408 			//  nulls for params 11-14
5409 			statement.setNull(11, java.sql.Types.BIGINT);
5410 			statement.setNull(12, java.sql.Types.BIGINT);
5411 			statement.setNull(13, java.sql.Types.BIGINT);
5412 			statement.setNull(14, java.sql.Types.BIGINT);
5413 
5414 			statement.setNull(15, java.sql.Types.VARCHAR); // MD5
5415 			statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
5416 			statement.setNull(17, java.sql.Types.VARCHAR); // MIME type
5417 
5418 			// parent path
5419 			statement.setString(18, parentPath);
5420 
5421 			// data source object id
5422 			long dataSourceObjectId = getDataSourceObjectId(connection, parentId);
5423 			statement.setLong(19, dataSourceObjectId);
5424 
5425 			//extension, since this is a directory we just set it to null
5426 			statement.setString(20, null);
5427 
5428 			connection.executeUpdate(statement);
5429 
5430 			return new LocalDirectory(this, newObjId, dataSourceObjectId, directoryName, dirType,
5431 					metaType, dirFlag, metaFlags, null, FileKnown.UNKNOWN,
5432 					parentPath);
5433 		} catch (SQLException e) {
5434 			throw new TskCoreException("Error creating local directory '" + directoryName + "'", e);
5435 		} finally {
5436 			closeResultSet(resultSet);
5437 			// NOTE: write lock will be released by transaction
5438 		}
5439 	}
5440 
5441 	/**
5442 	 * Adds a local/logical files and/or directories data source.
5443 	 *
5444 	 * @param deviceId          An ASCII-printable identifier for the device
5445 	 *                          associated with the data source that is intended
5446 	 *                          to be unique across multiple cases (e.g., a
5447 	 *                          UUID).
5448 	 * @param rootDirectoryName The name for the root virtual directory for the
5449 	 *                          data source.
5450 	 * @param timeZone          The time zone used to process the data source,
5451 	 *                          may be the empty string.
5452 	 * @param transaction       A transaction in the scope of which the
5453 	 *                          operation is to be performed, managed by the
5454 	 *                          caller.
5455 	 *
5456 	 * @return The new local files data source.
5457 	 *
5458 	 * @throws TskCoreException if there is an error adding the data source.
5459 	 */
addLocalFilesDataSource(String deviceId, String rootDirectoryName, String timeZone, CaseDbTransaction transaction)5460 	public LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootDirectoryName, String timeZone, CaseDbTransaction transaction) throws TskCoreException {
5461 		acquireSingleUserCaseWriteLock();
5462 		Statement statement = null;
5463 		try {
5464 			// Insert a row for the root virtual directory of the data source
5465 			// into the tsk_objects table.
5466 			CaseDbConnection connection = transaction.getConnection();
5467 			long newObjId = addObject(0, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
5468 
5469 			// Insert a row for the virtual directory of the data source into
5470 			// the data_source_info table.
5471 			statement = connection.createStatement();
5472 			statement.executeUpdate("INSERT INTO data_source_info (obj_id, device_id, time_zone) "
5473 					+ "VALUES(" + newObjId + ", '" + deviceId + "', '" + timeZone + "');");
5474 
5475 			// Insert a row for the root virtual directory of the data source
5476 			// into the tsk_files table. Note that its data source object id is
5477 			// its own object id.
5478 			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path,
5479 			// dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime,
5480 			// atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id, extension)
5481 			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
5482 			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
5483 			preparedStatement.clearParameters();
5484 			preparedStatement.setLong(1, newObjId);
5485 			preparedStatement.setNull(2, java.sql.Types.BIGINT);
5486 			preparedStatement.setString(3, rootDirectoryName);
5487 			preparedStatement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType());
5488 			preparedStatement.setShort(5, (short) 1);
5489 			TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
5490 			preparedStatement.setShort(6, TSK_FS_NAME_TYPE_ENUM.DIR.getValue());
5491 			TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
5492 			preparedStatement.setShort(7, metaType.getValue());
5493 			TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
5494 			preparedStatement.setShort(8, dirFlag.getValue());
5495 			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
5496 					| TSK_FS_META_FLAG_ENUM.USED.getValue());
5497 			preparedStatement.setShort(9, metaFlags);
5498 			preparedStatement.setLong(10, 0);
5499 			preparedStatement.setNull(11, java.sql.Types.BIGINT);
5500 			preparedStatement.setNull(12, java.sql.Types.BIGINT);
5501 			preparedStatement.setNull(13, java.sql.Types.BIGINT);
5502 			preparedStatement.setNull(14, java.sql.Types.BIGINT);
5503 			preparedStatement.setNull(15, java.sql.Types.VARCHAR); // MD5
5504 			preparedStatement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
5505 			preparedStatement.setNull(17, java.sql.Types.VARCHAR); // MIME type
5506 			String parentPath = "/"; //NON-NLS
5507 			preparedStatement.setString(18, parentPath);
5508 			preparedStatement.setLong(19, newObjId);
5509 			preparedStatement.setString(20, null); //extension, just set it to null
5510 			connection.executeUpdate(preparedStatement);
5511 
5512 			return new LocalFilesDataSource(this, newObjId, newObjId, deviceId, rootDirectoryName, dirType, metaType, dirFlag, metaFlags, timeZone, null, FileKnown.UNKNOWN, parentPath);
5513 
5514 		} catch (SQLException ex) {
5515 			throw new TskCoreException(String.format("Error creating local files data source with device id %s and directory name %s", deviceId, rootDirectoryName), ex);
5516 		} finally {
5517 			closeStatement(statement);
5518 			releaseSingleUserCaseWriteLock();
5519 		}
5520 	}
5521 
5522 	/**
5523 	 * Add an image to the database.
5524 	 *
5525 	 * @param type        Type of image
5526 	 * @param sectorSize  Sector size
5527 	 * @param size        Image size
5528 	 * @param displayName Display name for the image
5529 	 * @param imagePaths  Image path(s)
5530 	 * @param timezone    Time zone
5531 	 * @param md5         MD5 hash
5532 	 * @param sha1        SHA1 hash
5533 	 * @param sha256      SHA256 hash
5534 	 * @param deviceId    Device ID
5535 	 * @param transaction Case DB transaction
5536 	 *
5537 	 * @return the newly added Image
5538 	 *
5539 	 * @throws TskCoreException
5540 	 */
addImage(TskData.TSK_IMG_TYPE_ENUM type, long sectorSize, long size, String displayName, List<String> imagePaths, String timezone, String md5, String sha1, String sha256, String deviceId, CaseDbTransaction transaction)5541 	public Image addImage(TskData.TSK_IMG_TYPE_ENUM type, long sectorSize, long size, String displayName, List<String> imagePaths,
5542 			String timezone, String md5, String sha1, String sha256,
5543 			String deviceId,
5544 			CaseDbTransaction transaction) throws TskCoreException {
5545 		acquireSingleUserCaseWriteLock();
5546 		Statement statement = null;
5547 		try {
5548 			// Insert a row for the Image into the tsk_objects table.
5549 			CaseDbConnection connection = transaction.getConnection();
5550 			long newObjId = addObject(0, TskData.ObjectType.IMG.getObjectType(), connection);
5551 
5552 			// Add a row to tsk_image_info
5553 			// INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, sha1, sha256, display_name)
5554 			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_INFO);
5555 			preparedStatement.clearParameters();
5556 			preparedStatement.setLong(1, newObjId);
5557 			preparedStatement.setShort(2, (short) type.getValue());
5558 			preparedStatement.setLong(3, sectorSize);
5559 			preparedStatement.setString(4, timezone);
5560 			//prevent negative size
5561 			long savedSize = size < 0 ? 0 : size;
5562 			preparedStatement.setLong(5, savedSize);
5563 			preparedStatement.setString(6, md5);
5564 			preparedStatement.setString(7, sha1);
5565 			preparedStatement.setString(8, sha256);
5566 			preparedStatement.setString(9, displayName);
5567 			connection.executeUpdate(preparedStatement);
5568 
5569 			// If there are paths, add them to tsk_image_names
5570 			for (int i = 0; i < imagePaths.size(); i++) {
5571 				preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME);
5572 				preparedStatement.clearParameters();
5573 				preparedStatement.setLong(1, newObjId);
5574 				preparedStatement.setString(2, imagePaths.get(i));
5575 				preparedStatement.setLong(3, i);
5576 				connection.executeUpdate(preparedStatement);
5577 			}
5578 
5579 			// Add a row to data_source_info
5580 			preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_DATA_SOURCE_INFO);
5581 			statement = connection.createStatement();
5582 			preparedStatement.setLong(1, newObjId);
5583 			preparedStatement.setString(2, deviceId);
5584 			preparedStatement.setString(3, timezone);
5585 			connection.executeUpdate(preparedStatement);
5586 
5587 			// Create the new Image object
5588 			return new Image(this, newObjId, type.getValue(), deviceId, sectorSize, displayName,
5589 					imagePaths.toArray(new String[imagePaths.size()]), timezone, md5, sha1, sha256, savedSize);
5590 		} catch (SQLException ex) {
5591 			if (!imagePaths.isEmpty()) {
5592 				throw new TskCoreException(String.format("Error adding image with path %s to database", imagePaths.get(0)), ex);
5593 			} else {
5594 				throw new TskCoreException(String.format("Error adding image with display name %s to database", displayName), ex);
5595 			}
5596 		} finally {
5597 			closeStatement(statement);
5598 			releaseSingleUserCaseWriteLock();
5599 		}
5600 	}
5601 
5602 	/**
5603 	 * Add a volume system to the database.
5604 	 *
5605 	 * @param parentObjId Object ID of the volume system's parent
5606 	 * @param type        Type of volume system
5607 	 * @param imgOffset   Image offset
5608 	 * @param blockSize   Block size
5609 	 * @param transaction Case DB transaction
5610 	 *
5611 	 * @return the newly added VolumeSystem
5612 	 *
5613 	 * @throws TskCoreException
5614 	 */
5615 	public VolumeSystem addVolumeSystem(long parentObjId, TskData.TSK_VS_TYPE_ENUM type, long imgOffset,
5616 			long blockSize, CaseDbTransaction transaction) throws TskCoreException {
5617 		acquireSingleUserCaseWriteLock();
5618 		try {
5619 			// Insert a row for the VolumeSystem into the tsk_objects table.
5620 			CaseDbConnection connection = transaction.getConnection();
5621 			long newObjId = addObject(parentObjId, TskData.ObjectType.VS.getObjectType(), connection);
5622 
5623 			// Add a row to tsk_vs_info
5624 			// INSERT INTO tsk_vs_info (obj_id, vs_type, img_offset, block_size)
5625 			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_VS_INFO);
5626 			preparedStatement.clearParameters();
5627 			preparedStatement.setLong(1, newObjId);
5628 			preparedStatement.setShort(2, (short) type.getVsType());
5629 			preparedStatement.setLong(3, imgOffset);
5630 			preparedStatement.setLong(4, blockSize);
5631 			connection.executeUpdate(preparedStatement);
5632 
5633 			// Create the new VolumeSystem object
5634 			return new VolumeSystem(this, newObjId, "", type.getVsType(), imgOffset, blockSize);
5635 		} catch (SQLException ex) {
5636 			throw new TskCoreException(String.format("Error creating volume system with parent ID %d and image offset %d",
5637 					parentObjId, imgOffset), ex);
5638 		} finally {
5639 			releaseSingleUserCaseWriteLock();
5640 		}
5641 	}
5642 
5643 	/**
5644 	 * Add a volume to the database
5645 	 *
5646 	 * @param parentObjId Object ID of the volume's parent
5647 	 * @param addr			     Address of the volume
5648 	 * @param start       Start of the volume
5649 	 * @param length      Length of the volume
5650 	 * @param desc        Description of the volume
5651 	 * @param flags       Flags
5652 	 * @param transaction Case DB transaction
5653 	 *
5654 	 * @return the newly created Volume
5655 	 *
5656 	 * @throws TskCoreException
5657 	 */
5658 	public Volume addVolume(long parentObjId, long addr, long start, long length, String desc,
5659 			long flags, CaseDbTransaction transaction) throws TskCoreException {
5660 		acquireSingleUserCaseWriteLock();
5661 		Statement statement = null;
5662 		try {
5663 			// Insert a row for the Volume into the tsk_objects table.
5664 			CaseDbConnection connection = transaction.getConnection();
5665 			long newObjId = addObject(parentObjId, TskData.ObjectType.VOL.getObjectType(), connection);
5666 
5667 			// Add a row to tsk_vs_parts
5668 			// INSERT INTO tsk_vs_parts (obj_id, addr, start, length, desc, flags)
5669 			PreparedStatement preparedStatement;
5670 			if (this.dbType == DbType.POSTGRESQL) {
5671 				preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_VS_PART_POSTGRESQL);
5672 			} else {
5673 				preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_VS_PART_SQLITE);
5674 			}
5675 			preparedStatement.clearParameters();
5676 			preparedStatement.setLong(1, newObjId);
5677 			preparedStatement.setLong(2, addr);
5678 			preparedStatement.setLong(3, start);
5679 			preparedStatement.setLong(4, length);
5680 			preparedStatement.setString(5, desc);
5681 			preparedStatement.setShort(6, (short) flags);
5682 			connection.executeUpdate(preparedStatement);
5683 
5684 			// Create the new Volume object
5685 			return new Volume(this, newObjId, addr, start, length, flags, desc);
5686 		} catch (SQLException ex) {
5687 			throw new TskCoreException(String.format("Error creating volume with address %d and parent ID %d", addr, parentObjId), ex);
5688 		} finally {
5689 			closeStatement(statement);
5690 			releaseSingleUserCaseWriteLock();
5691 		}
5692 	}
5693 
5694 	/**
5695 	 * Add a FileSystem to the database.
5696 	 *
5697 	 * @param parentObjId Object ID of the file system's parent
5698 	 * @param imgOffset   Offset in the image
5699 	 * @param type        Type of file system
5700 	 * @param blockSize   Block size
5701 	 * @param blockCount  Block count
5702 	 * @param rootInum    root inum
5703 	 * @param firstInum   first inum
5704 	 * @param lastInum    last inum
5705 	 * @param displayName display name
5706 	 * @param transaction Case DB transaction
5707 	 *
5708 	 * @return the newly created FileSystem
5709 	 *
5710 	 * @throws TskCoreException
5711 	 */
5712 	public FileSystem addFileSystem(long parentObjId, long imgOffset, TskData.TSK_FS_TYPE_ENUM type, long blockSize, long blockCount,
5713 			long rootInum, long firstInum, long lastInum, String displayName,
5714 			CaseDbTransaction transaction) throws TskCoreException {
5715 		acquireSingleUserCaseWriteLock();
5716 		Statement statement = null;
5717 		try {
5718 			// Insert a row for the FileSystem into the tsk_objects table.
5719 			CaseDbConnection connection = transaction.getConnection();
5720 			long newObjId = addObject(parentObjId, TskData.ObjectType.FS.getObjectType(), connection);
5721 
5722 			// Add a row to tsk_fs_info
5723 			// INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)
5724 			PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FS_INFO);
5725 			preparedStatement.clearParameters();
5726 			preparedStatement.setLong(1, newObjId);
5727 			preparedStatement.setLong(2, imgOffset);
5728 			preparedStatement.setShort(3, (short) type.getValue());
5729 			preparedStatement.setLong(4, blockSize);
5730 			preparedStatement.setLong(5, blockCount);
5731 			preparedStatement.setLong(6, rootInum);
5732 			preparedStatement.setLong(7, firstInum);
5733 			preparedStatement.setLong(8, lastInum);
5734 			preparedStatement.setString(9, displayName);
5735 			connection.executeUpdate(preparedStatement);
5736 
5737 			// Create the new FileSystem object
5738 			return new FileSystem(this, newObjId, displayName, imgOffset, type, blockSize, blockCount, rootInum,
5739 					firstInum, lastInum);
5740 		} catch (SQLException ex) {
5741 			throw new TskCoreException(String.format("Error creating file system with image offset %d and parent ID %d",
5742 					imgOffset, parentObjId), ex);
5743 		} finally {
5744 			closeStatement(statement);
5745 			releaseSingleUserCaseWriteLock();
5746 		}
5747 	}
5748 
5749 	/**
5750 	 * Add a file system file.
5751 	 *
5752 	 * @param dataSourceObjId	The object id of the root data source of this
5753 	 *							file.
5754 	 * @param fsObjId		The file system object id.
5755 	 * @param fileName		The name of the file.
5756 	 * @param metaAddr		The meta address of the file.
5757 	 * @param metaSeq		The meta address sequence of the file.
5758 	 * @param attrType		The attributed type of the file.
5759 	 * @param attrId		The attribute id
5760 	 * @param dirFlag		The allocated status from the name structure
5761 	 * @param metaFlags
5762 	 * @param size			The size of the file in bytes.
5763 	 * @param ctime			The changed time of the file.
5764 	 * @param crtime		The creation time of the file.
5765 	 * @param atime			The accessed time of the file
5766 	 * @param mtime			The modified time of the file.
5767 	 ** @param isFile		True, unless the file is a directory.
5768 	 * @param parent		The parent of the file (e.g., a virtual directory)
5769 	 *
5770 	 * @return Newly created file
5771 	 *
5772 	 * @throws TskCoreException
5773 	 */
5774 	public FsContent addFileSystemFile(long dataSourceObjId, long fsObjId,
5775 										String fileName,
5776 										long metaAddr, int metaSeq,
5777 										TSK_FS_ATTR_TYPE_ENUM attrType, int attrId,
5778 										TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size,
5779 										long ctime, long crtime, long atime, long mtime,
5780 										boolean isFile, Content parent) throws TskCoreException {
5781 
5782 		CaseDbTransaction transaction = beginTransaction();
5783 		Statement queryStatement = null;
5784 		try {
5785 			CaseDbConnection connection = transaction.getConnection();
5786 			transaction.acquireSingleUserCaseWriteLock();
5787 
5788 			// Insert a row for the local/logical file into the tsk_objects table.
5789 			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
5790 			long objectId = addObject(parent.getId(), TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
5791 
5792 			String parentPath;
5793 
5794 			if (parent instanceof AbstractFile) {
5795 				AbstractFile parentFile = (AbstractFile) parent;
5796 				if (isRootDirectory(parentFile, transaction)) {
5797 					parentPath = "/";
5798 				} else {
5799 					parentPath = parentFile.getParentPath() + parent.getName() + "/"; //NON-NLS
5800 				}
5801 			} else {
5802 				parentPath = "/";
5803 			}
5804 
5805 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE_SYSTEM_FILE);
5806 			statement.clearParameters();
5807 			statement.setLong(1, objectId);											// obj_is
5808 			statement.setLong(2, fsObjId);											// fs_obj_id
5809 			statement.setLong(3, dataSourceObjId);									// data_source_obj_id
5810 			statement.setShort(4, (short)attrType.getValue());						// attr_type
5811 			statement.setInt(5, attrId);											// attr_id
5812 			statement.setString(6, fileName);										// name
5813 			statement.setLong(7, metaAddr);											// meta_addr
5814 			statement.setInt(8, metaSeq);											// meta_addr
5815 			statement.setShort(9, TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType());	//type
5816 			statement.setShort(10, (short) 1);										// has_path
5817 			TSK_FS_NAME_TYPE_ENUM dirType = isFile ? TSK_FS_NAME_TYPE_ENUM.REG : TSK_FS_NAME_TYPE_ENUM.DIR;
5818 			statement.setShort(11, dirType.getValue());								// dir_type
5819 			TSK_FS_META_TYPE_ENUM metaType = isFile ? TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG : TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
5820 			statement.setShort(12, metaType.getValue());							// meta_type
5821 			statement.setShort(13, dirFlag.getValue());								// dir_flags
5822 			statement.setShort(14, metaFlags);										// meta_flags
5823 			statement.setLong(15,  size < 0 ? 0 : size);
5824 			statement.setLong(16, ctime);
5825 			statement.setLong(17, crtime);
5826 			statement.setLong(18, atime);
5827 			statement.setLong(19, mtime);
5828 			statement.setString(20, parentPath);
5829 			final String extension = extractExtension(fileName);
5830 			statement.setString(21, extension);
5831 
5832 			connection.executeUpdate(statement);
5833 
5834 			transaction.commit();
5835 			transaction = null;
5836 
5837 			return new org.sleuthkit.datamodel.File(this, objectId, dataSourceObjId, fsObjId,
5838 			attrType, attrId, fileName, metaAddr, metaSeq,
5839 			dirType, metaType, dirFlag, metaFlags,
5840 			size, ctime, crtime, atime, mtime,
5841 			(short)0, 0, 0, null, null, parentPath, null,
5842 			extension);
5843 
5844 		} catch(SQLException ex) {
5845 			logger.log(Level.WARNING, "Failed to add file system file", ex);
5846 		}
5847 		finally {
5848 			closeStatement(queryStatement);
5849 			if (null != transaction) {
5850 				try {
5851 					transaction.rollback();
5852 				} catch (TskCoreException ex2) {
5853 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
5854 				}
5855 			}
5856 		}
5857 		return null;
5858 	}
5859 
5860 	/**
5861 	 * Get IDs of the virtual folder roots (at the same level as image), used
5862 	 * for containers such as for local files.
5863 	 *
5864 	 * @return IDs of virtual directory root objects.
5865 	 *
5866 	 * @throws org.sleuthkit.datamodel.TskCoreException
5867 	 */
5868 	public List<VirtualDirectory> getVirtualDirectoryRoots() throws TskCoreException {
5869 		CaseDbConnection connection = connections.getConnection();
5870 		acquireSingleUserCaseReadLock();
5871 		Statement s = null;
5872 		ResultSet rs = null;
5873 		try {
5874 			s = connection.createStatement();
5875 			rs = connection.executeQuery(s, "SELECT * FROM tsk_files WHERE" //NON-NLS
5876 					+ " type = " + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
5877 					+ " AND obj_id = data_source_obj_id"
5878 					+ " ORDER BY dir_type, LOWER(name)"); //NON-NLS
5879 			List<VirtualDirectory> virtDirRootIds = new ArrayList<VirtualDirectory>();
5880 			while (rs.next()) {
5881 				virtDirRootIds.add(virtualDirectory(rs, connection));
5882 			}
5883 			return virtDirRootIds;
5884 		} catch (SQLException ex) {
5885 			throw new TskCoreException("Error getting local files virtual folder id", ex);
5886 		} finally {
5887 			closeResultSet(rs);
5888 			closeStatement(s);
5889 			connection.close();
5890 			releaseSingleUserCaseReadLock();
5891 		}
5892 	}
5893 
5894 	/**
5895 	 * Adds one or more layout files for a parent Content object to the case
5896 	 * database.
5897 	 *
5898 	 * @param parent     The parent Content.
5899 	 * @param fileRanges File range objects for the file(s).
5900 	 *
5901 	 * @return A list of LayoutFile objects.
5902 	 *
5903 	 * @throws TskCoreException If there is a problem completing a case database
5904 	 *                          operation.
5905 	 */
5906 	public final List<LayoutFile> addLayoutFiles(Content parent, List<TskFileRange> fileRanges) throws TskCoreException {
5907 		assert (null != fileRanges);
5908 		if (null == fileRanges) {
5909 			throw new TskCoreException("TskFileRange object is null");
5910 		}
5911 
5912 		assert (null != parent);
5913 		if (null == parent) {
5914 			throw new TskCoreException("Conent is null");
5915 		}
5916 
5917 		CaseDbTransaction transaction = null;
5918 		Statement statement = null;
5919 		ResultSet resultSet = null;
5920 
5921 		try {
5922 			transaction = beginTransaction();
5923 			transaction.acquireSingleUserCaseWriteLock();
5924 			CaseDbConnection connection = transaction.getConnection();
5925 
5926 			List<LayoutFile> fileRangeLayoutFiles = new ArrayList<LayoutFile>();
5927 			for (TskFileRange fileRange : fileRanges) {
5928 				/*
5929 				 * Insert a row for the Tsk file range into the tsk_objects
5930 				 * table: INSERT INTO tsk_objects (par_obj_id, type) VALUES (?,
5931 				 * ?)
5932 				 */
5933 				long fileRangeId = addObject(parent.getId(), TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
5934 				long end_byte_in_parent = fileRange.getByteStart() + fileRange.getByteLen() - 1;
5935 				/*
5936 				 * Insert a row for the Tsk file range into the tsk_files table:
5937 				 * INSERT INTO tsk_files (obj_id, fs_obj_id, name, type,
5938 				 * has_path, dir_type, meta_type, dir_flags, meta_flags, size,
5939 				 * ctime, crtime, atime, mtime, md5, known, mime_type,
5940 				 * parent_path, data_source_obj_id,extension) VALUES (?, ?, ?,
5941 				 * ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
5942 				 */
5943 				PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
5944 				prepStmt.clearParameters();
5945 				prepStmt.setLong(1, fileRangeId); // obj_id	from tsk_objects
5946 				prepStmt.setNull(2, java.sql.Types.BIGINT); // fs_obj_id
5947 				prepStmt.setString(3, "Unalloc_" + parent.getId() + "_" + fileRange.getByteStart() + "_" + end_byte_in_parent); // name of form Unalloc_[image obj_id]_[start byte in parent]_[end byte in parent]
5948 				prepStmt.setShort(4, TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType()); // type
5949 				prepStmt.setNull(5, java.sql.Types.BIGINT); // has_path
5950 				prepStmt.setShort(6, TSK_FS_NAME_TYPE_ENUM.REG.getValue()); // dir_type
5951 				prepStmt.setShort(7, TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue()); // meta_type
5952 				prepStmt.setShort(8, TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()); // dir_flags
5953 				prepStmt.setShort(9, TSK_FS_META_FLAG_ENUM.UNALLOC.getValue()); // nmeta_flags
5954 				prepStmt.setLong(10, fileRange.getByteLen()); // size
5955 				prepStmt.setNull(11, java.sql.Types.BIGINT); // ctime
5956 				prepStmt.setNull(12, java.sql.Types.BIGINT); // crtime
5957 				prepStmt.setNull(13, java.sql.Types.BIGINT); // atime
5958 				prepStmt.setNull(14, java.sql.Types.BIGINT); // mtime
5959 				prepStmt.setNull(15, java.sql.Types.VARCHAR); // MD5
5960 				prepStmt.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
5961 				prepStmt.setNull(17, java.sql.Types.VARCHAR); // MIME type
5962 				prepStmt.setNull(18, java.sql.Types.VARCHAR); // parent path
5963 				prepStmt.setLong(19, parent.getId()); // data_source_obj_id
5964 
5965 				//extension, since this is not a FS file we just set it to null
5966 				prepStmt.setString(20, null);
5967 				connection.executeUpdate(prepStmt);
5968 
5969 				/*
5970 				 * Insert a row in the tsk_layout_file table for each chunk of
5971 				 * the carved file. INSERT INTO tsk_file_layout (obj_id,
5972 				 * byte_start, byte_len, sequence) VALUES (?, ?, ?, ?)
5973 				 */
5974 				prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LAYOUT_FILE);
5975 				prepStmt.clearParameters();
5976 				prepStmt.setLong(1, fileRangeId); // obj_id
5977 				prepStmt.setLong(2, fileRange.getByteStart()); // byte_start
5978 				prepStmt.setLong(3, fileRange.getByteLen()); // byte_len
5979 				prepStmt.setLong(4, fileRange.getSequence()); // sequence
5980 				connection.executeUpdate(prepStmt);
5981 
5982 				/*
5983 				 * Create a layout file representation of the carved file.
5984 				 */
5985 				fileRangeLayoutFiles.add(new LayoutFile(this,
5986 						fileRangeId,
5987 						parent.getId(),
5988 						Long.toString(fileRange.getSequence()),
5989 						TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS,
5990 						TSK_FS_NAME_TYPE_ENUM.REG,
5991 						TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG,
5992 						TSK_FS_NAME_FLAG_ENUM.UNALLOC,
5993 						TSK_FS_META_FLAG_ENUM.UNALLOC.getValue(),
5994 						fileRange.getByteLen(),
5995 						0L, 0L, 0L, 0L,
5996 						null,
5997 						FileKnown.UNKNOWN,
5998 						parent.getUniquePath(),
5999 						null));
6000 			}
6001 
6002 			transaction.commit();
6003 			transaction = null;
6004 			return fileRangeLayoutFiles;
6005 
6006 		} catch (SQLException ex) {
6007 			throw new TskCoreException("Failed to add layout files to case database", ex);
6008 		} finally {
6009 			closeResultSet(resultSet);
6010 			closeStatement(statement);
6011 
6012 			// NOTE: write lock will be released by transaction
6013 			if (null != transaction) {
6014 				try {
6015 					transaction.rollback();
6016 				} catch (TskCoreException ex2) {
6017 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
6018 				}
6019 			}
6020 		}
6021 	}
6022 
6023 	/**
6024 	 * Adds a carving result to the case database.
6025 	 *
6026 	 * @param carvingResult The carving result (a set of carved files and their
6027 	 *                      parent) to be added.
6028 	 *
6029 	 * @return A list of LayoutFile representations of the carved files.
6030 	 *
6031 	 * @throws TskCoreException If there is a problem completing a case database
6032 	 *                          operation.
6033 	 */
6034 	public final List<LayoutFile> addCarvedFiles(CarvingResult carvingResult) throws TskCoreException {
6035 		assert (null != carvingResult);
6036 		if (null == carvingResult) {
6037 			throw new TskCoreException("Carving is null");
6038 		}
6039 		assert (null != carvingResult.getParent());
6040 		if (null == carvingResult.getParent()) {
6041 			throw new TskCoreException("Carving result has null parent");
6042 		}
6043 		assert (null != carvingResult.getCarvedFiles());
6044 		if (null == carvingResult.getCarvedFiles()) {
6045 			throw new TskCoreException("Carving result has null carved files");
6046 		}
6047 		CaseDbTransaction transaction = null;
6048 		Statement statement = null;
6049 		ResultSet resultSet = null;
6050 		long newCacheKey = 0; // Used to roll back cache if transaction is rolled back.
6051 		try {
6052 			transaction = beginTransaction();
6053 			transaction.acquireSingleUserCaseWriteLock();
6054 			CaseDbConnection connection = transaction.getConnection();
6055 
6056 			/*
6057 			 * Carved files are "re-parented" as children of the $CarvedFiles
6058 			 * virtual directory of the root file system, volume, or image
6059 			 * ancestor of the carved files parent, but if no such ancestor is
6060 			 * found, then the parent specified in the carving result is used.
6061 			 */
6062 			Content root = carvingResult.getParent();
6063 			while (null != root) {
6064 				if (root instanceof FileSystem || root instanceof Volume || root instanceof Image) {
6065 					break;
6066 				}
6067 				root = root.getParent();
6068 			}
6069 			if (null == root) {
6070 				root = carvingResult.getParent();
6071 			}
6072 
6073 			/*
6074 			 * Get or create the $CarvedFiles virtual directory for the root
6075 			 * ancestor.
6076 			 */
6077 			VirtualDirectory carvedFilesDir = rootIdsToCarvedFileDirs.get(root.getId());
6078 			if (null == carvedFilesDir) {
6079 				List<Content> rootChildren;
6080 				if (root instanceof FileSystem) {
6081 					rootChildren = ((FileSystem) root).getRootDirectory().getChildren();
6082 				} else {
6083 					rootChildren = root.getChildren();
6084 				}
6085 				for (Content child : rootChildren) {
6086 					if (child instanceof VirtualDirectory && child.getName().equals(VirtualDirectory.NAME_CARVED)) {
6087 						carvedFilesDir = (VirtualDirectory) child;
6088 						break;
6089 					}
6090 				}
6091 				if (null == carvedFilesDir) {
6092 					long parId = root.getId();
6093 					// $CarvedFiles should be a child of the root directory, not the file system
6094 					if (root instanceof FileSystem) {
6095 						Content rootDir = ((FileSystem) root).getRootDirectory();
6096 						parId = rootDir.getId();
6097 					}
6098 					carvedFilesDir = addVirtualDirectory(parId, VirtualDirectory.NAME_CARVED, transaction);
6099 				}
6100 				newCacheKey = root.getId();
6101 				rootIdsToCarvedFileDirs.put(newCacheKey, carvedFilesDir);
6102 			}
6103 
6104 			/*
6105 			 * Add the carved files to the database as children of the
6106 			 * $CarvedFile directory of the root ancestor.
6107 			 */
6108 			String parentPath = getFileParentPath(carvedFilesDir.getId(), connection) + carvedFilesDir.getName() + "/";
6109 			List<LayoutFile> carvedFiles = new ArrayList<LayoutFile>();
6110 			for (CarvingResult.CarvedFile carvedFile : carvingResult.getCarvedFiles()) {
6111 				/*
6112 				 * Insert a row for the carved file into the tsk_objects table:
6113 				 * INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
6114 				 */
6115 				long carvedFileId = addObject(carvedFilesDir.getId(), TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
6116 
6117 				/*
6118 				 * Insert a row for the carved file into the tsk_files table:
6119 				 * INSERT INTO tsk_files (obj_id, fs_obj_id, name, type,
6120 				 * has_path, dir_type, meta_type, dir_flags, meta_flags, size,
6121 				 * ctime, crtime, atime, mtime, md5, known, mime_type,
6122 				 * parent_path, data_source_obj_id,extenion) VALUES (?, ?, ?, ?,
6123 				 * ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
6124 				 */
6125 				PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
6126 				prepStmt.clearParameters();
6127 				prepStmt.setLong(1, carvedFileId); // obj_id
6128 				if (root instanceof FileSystem) {
6129 					prepStmt.setLong(2, root.getId()); // fs_obj_id
6130 				} else {
6131 					prepStmt.setNull(2, java.sql.Types.BIGINT); // fs_obj_id
6132 				}
6133 				prepStmt.setString(3, carvedFile.getName()); // name
6134 				prepStmt.setShort(4, TSK_DB_FILES_TYPE_ENUM.CARVED.getFileType()); // type
6135 				prepStmt.setShort(5, (short) 1); // has_path
6136 				prepStmt.setShort(6, TSK_FS_NAME_TYPE_ENUM.REG.getValue()); // dir_type
6137 				prepStmt.setShort(7, TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue()); // meta_type
6138 				prepStmt.setShort(8, TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()); // dir_flags
6139 				prepStmt.setShort(9, TSK_FS_META_FLAG_ENUM.UNALLOC.getValue()); // nmeta_flags
6140 				prepStmt.setLong(10, carvedFile.getSizeInBytes()); // size
6141 				prepStmt.setNull(11, java.sql.Types.BIGINT); // ctime
6142 				prepStmt.setNull(12, java.sql.Types.BIGINT); // crtime
6143 				prepStmt.setNull(13, java.sql.Types.BIGINT); // atime
6144 				prepStmt.setNull(14, java.sql.Types.BIGINT); // mtime
6145 				prepStmt.setNull(15, java.sql.Types.VARCHAR); // MD5
6146 				prepStmt.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
6147 				prepStmt.setNull(17, java.sql.Types.VARCHAR); // MIME type
6148 				prepStmt.setString(18, parentPath); // parent path
6149 				prepStmt.setLong(19, carvedFilesDir.getDataSourceObjectId()); // data_source_obj_id
6150 				prepStmt.setString(20, extractExtension(carvedFile.getName())); //extension
6151 				connection.executeUpdate(prepStmt);
6152 
6153 				/*
6154 				 * Insert a row in the tsk_layout_file table for each chunk of
6155 				 * the carved file. INSERT INTO tsk_file_layout (obj_id,
6156 				 * byte_start, byte_len, sequence) VALUES (?, ?, ?, ?)
6157 				 */
6158 				prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LAYOUT_FILE);
6159 				for (TskFileRange tskFileRange : carvedFile.getLayoutInParent()) {
6160 					prepStmt.clearParameters();
6161 					prepStmt.setLong(1, carvedFileId); // obj_id
6162 					prepStmt.setLong(2, tskFileRange.getByteStart()); // byte_start
6163 					prepStmt.setLong(3, tskFileRange.getByteLen()); // byte_len
6164 					prepStmt.setLong(4, tskFileRange.getSequence()); // sequence
6165 					connection.executeUpdate(prepStmt);
6166 				}
6167 
6168 				/*
6169 				 * Create a layout file representation of the carved file.
6170 				 */
6171 				carvedFiles.add(new LayoutFile(this,
6172 						carvedFileId,
6173 						carvedFilesDir.getDataSourceObjectId(),
6174 						carvedFile.getName(),
6175 						TSK_DB_FILES_TYPE_ENUM.CARVED,
6176 						TSK_FS_NAME_TYPE_ENUM.REG,
6177 						TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG,
6178 						TSK_FS_NAME_FLAG_ENUM.UNALLOC,
6179 						TSK_FS_META_FLAG_ENUM.UNALLOC.getValue(),
6180 						carvedFile.getSizeInBytes(),
6181 						0L, 0L, 0L, 0L,
6182 						null,
6183 						FileKnown.UNKNOWN,
6184 						parentPath,
6185 						null));
6186 			}
6187 
6188 			transaction.commit();
6189 			transaction = null;
6190 			return carvedFiles;
6191 
6192 		} catch (SQLException ex) {
6193 			throw new TskCoreException("Failed to add carved files to case database", ex);
6194 		} finally {
6195 			closeResultSet(resultSet);
6196 			closeStatement(statement);
6197 
6198 			// NOTE: write lock will be released by transaction
6199 			if (null != transaction) {
6200 				try {
6201 					transaction.rollback();
6202 				} catch (TskCoreException ex2) {
6203 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
6204 				}
6205 				if (0 != newCacheKey) {
6206 					rootIdsToCarvedFileDirs.remove(newCacheKey);
6207 				}
6208 			}
6209 		}
6210 	}
6211 
6212 	/**
6213 	 * Creates a new derived file object, adds it to database and returns it.
6214 	 *
6215 	 * TODO add support for adding derived method
6216 	 *
6217 	 * @param fileName        file name the derived file
6218 	 * @param localPath       local path of the derived file, including the file
6219 	 *                        name. The path is relative to the database path.
6220 	 * @param size            size of the derived file in bytes
6221 	 * @param ctime           The changed time of the file.
6222 	 * @param crtime          The creation time of the file.
6223 	 * @param atime           The accessed time of the file
6224 	 * @param mtime           The modified time of the file.
6225 	 * @param isFile          whether a file or directory, true if a file
6226 	 * @param parentObj		     parent content object
6227 	 * @param rederiveDetails details needed to re-derive file (will be specific
6228 	 *                        to the derivation method), currently unused
6229 	 * @param toolName        name of derivation method/tool, currently unused
6230 	 * @param toolVersion     version of derivation method/tool, currently
6231 	 *                        unused
6232 	 * @param otherDetails    details of derivation method/tool, currently
6233 	 *                        unused
6234 	 * @param encodingType    Type of encoding used on the file (or NONE if no
6235 	 *                        encoding)
6236 	 *
6237 	 * @return newly created derived file object
6238 	 *
6239 	 * @throws TskCoreException exception thrown if the object creation failed
6240 	 *                          due to a critical system error
6241 	 */
6242 	public DerivedFile addDerivedFile(String fileName, String localPath,
6243 			long size, long ctime, long crtime, long atime, long mtime,
6244 			boolean isFile, Content parentObj,
6245 			String rederiveDetails, String toolName, String toolVersion,
6246 			String otherDetails, TskData.EncodingType encodingType) throws TskCoreException {
6247 		// Strip off any leading slashes from the local path (leading slashes indicate absolute paths)
6248 		localPath = localPath.replaceAll("^[/\\\\]+", "");
6249 
6250 		acquireSingleUserCaseWriteLock();
6251 		TimelineManager timelineManager = getTimelineManager();
6252 
6253 		CaseDbTransaction transaction = beginTransaction();
6254 		CaseDbConnection connection = transaction.getConnection();
6255 		try {
6256 			final long parentId = parentObj.getId();
6257 			String parentPath = "";
6258 			if (parentObj instanceof BlackboardArtifact) {
6259 				parentPath = parentObj.getUniquePath() + '/' + parentObj.getName() + '/';
6260 			} else if (parentObj instanceof AbstractFile) {
6261 				parentPath = ((AbstractFile) parentObj).getParentPath() + parentObj.getName() + '/'; //NON-NLS
6262 			}
6263 
6264 			// Insert a row for the derived file into the tsk_objects table.
6265 			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
6266 			long newObjId = addObject(parentId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
6267 
6268 			// Insert a row for the virtual directory into the tsk_files table.
6269 			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
6270 			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type,
6271 			// parent_path, data_source_obj_id, extension)
6272 			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
6273 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
6274 			statement.clearParameters();
6275 			statement.setLong(1, newObjId);
6276 
6277 			// If the parentFile is part of a file system, use its file system object ID.
6278 			long fsObjId = this.getFileSystemId(parentId, connection);
6279 			if (fsObjId != -1) {
6280 				statement.setLong(2, fsObjId);
6281 			} else {
6282 				statement.setNull(2, java.sql.Types.BIGINT);
6283 			}
6284 			statement.setString(3, fileName);
6285 
6286 			//type, has_path
6287 			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.getFileType());
6288 			statement.setShort(5, (short) 1);
6289 
6290 			//flags
6291 			final TSK_FS_NAME_TYPE_ENUM dirType = isFile ? TSK_FS_NAME_TYPE_ENUM.REG : TSK_FS_NAME_TYPE_ENUM.DIR;
6292 			statement.setShort(6, dirType.getValue());
6293 			final TSK_FS_META_TYPE_ENUM metaType = isFile ? TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG : TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
6294 			statement.setShort(7, metaType.getValue());
6295 
6296 			//note: using alloc under assumption that derived files derive from alloc files
6297 			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
6298 			statement.setShort(8, dirFlag.getValue());
6299 			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
6300 					| TSK_FS_META_FLAG_ENUM.USED.getValue());
6301 			statement.setShort(9, metaFlags);
6302 
6303 			//size
6304 			//prevent negative size
6305 			long savedSize = size < 0 ? 0 : size;
6306 			statement.setLong(10, savedSize);
6307 
6308 			//mactimes
6309 			//long ctime, long crtime, long atime, long mtime,
6310 			statement.setLong(11, ctime);
6311 			statement.setLong(12, crtime);
6312 			statement.setLong(13, atime);
6313 			statement.setLong(14, mtime);
6314 
6315 			statement.setNull(15, java.sql.Types.VARCHAR); // MD5
6316 			statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
6317 			statement.setNull(17, java.sql.Types.VARCHAR); // MIME type
6318 
6319 			//parent path
6320 			statement.setString(18, parentPath);
6321 
6322 			// root data source object id
6323 			long dataSourceObjId = getDataSourceObjectId(connection, parentId);
6324 			statement.setLong(19, dataSourceObjId);
6325 			final String extension = extractExtension(fileName);
6326 			//extension
6327 			statement.setString(20, extension);
6328 
6329 			connection.executeUpdate(statement);
6330 
6331 			//add localPath
6332 			addFilePath(connection, newObjId, localPath, encodingType);
6333 
6334 			DerivedFile derivedFile = new DerivedFile(this, newObjId, dataSourceObjId, fileName, dirType, metaType, dirFlag, metaFlags,
6335 					savedSize, ctime, crtime, atime, mtime, null, null, parentPath, localPath, parentId, null, encodingType, extension);
6336 
6337 			timelineManager.addEventsForNewFile(derivedFile, connection);
6338 			transaction.commit();
6339 			//TODO add derived method to tsk_files_derived and tsk_files_derived_method
6340 			return derivedFile;
6341 		} catch (SQLException ex) {
6342 			connection.rollbackTransaction();
6343 			throw new TskCoreException("Failed to add derived file to case database", ex);
6344 		} finally {
6345 			connection.close();
6346 			releaseSingleUserCaseWriteLock();
6347 		}
6348 	}
6349 
6350 	/**
6351 	 * Updates an existing derived file in the database and returns a new
6352 	 * derived file object with the updated contents
6353 	 *
6354 	 * @param derivedFile	    The derived file you wish to update
6355 	 * @param localPath       local path of the derived file, including the file
6356 	 *                        name. The path is relative to the database path.
6357 	 * @param size            size of the derived file in bytes
6358 	 * @param ctime           The changed time of the file.
6359 	 * @param crtime          The creation time of the file.
6360 	 * @param atime           The accessed time of the file
6361 	 * @param mtime           The modified time of the file.
6362 	 * @param isFile          whether a file or directory, true if a file
6363 	 * @param mimeType		      The MIME type the updated file should have, null
6364 	 *                        to unset it
6365 	 * @param rederiveDetails details needed to re-derive file (will be specific
6366 	 *                        to the derivation method), currently unused
6367 	 * @param toolName        name of derivation method/tool, currently unused
6368 	 * @param toolVersion     version of derivation method/tool, currently
6369 	 *                        unused
6370 	 * @param otherDetails    details of derivation method/tool, currently
6371 	 *                        unused
6372 	 * @param encodingType    Type of encoding used on the file (or NONE if no
6373 	 *                        encoding)
6374 	 *
6375 	 * @return newly created derived file object which contains the updated data
6376 	 *
6377 	 * @throws TskCoreException exception thrown if the object creation failed
6378 	 *                          due to a critical system error
6379 	 */
6380 	public DerivedFile updateDerivedFile(DerivedFile derivedFile, String localPath,
6381 			long size, long ctime, long crtime, long atime, long mtime,
6382 			boolean isFile, String mimeType,
6383 			String rederiveDetails, String toolName, String toolVersion,
6384 			String otherDetails, TskData.EncodingType encodingType) throws TskCoreException {
6385 
6386 		// Strip off any leading slashes from the local path (leading slashes indicate absolute paths)
6387 		localPath = localPath.replaceAll("^[/\\\\]+", "");
6388 
6389 		CaseDbConnection connection = connections.getConnection();
6390 		acquireSingleUserCaseWriteLock();
6391 		ResultSet rs = null;
6392 		try {
6393 			Content parentObj = derivedFile.getParent();
6394 			connection.beginTransaction();
6395 			final long parentId = parentObj.getId();
6396 			String parentPath = "";
6397 			if (parentObj instanceof BlackboardArtifact) {
6398 				parentPath = parentObj.getUniquePath() + '/' + parentObj.getName() + '/';
6399 			} else if (parentObj instanceof AbstractFile) {
6400 				parentPath = ((AbstractFile) parentObj).getParentPath() + parentObj.getName() + '/'; //NON-NLS
6401 			}
6402 			// UPDATE tsk_files SET type = ?, dir_type = ?, meta_type = ?, dir_flags = ?,  meta_flags = ?, "
6403 			// + "size= ?, ctime= ?, crtime= ?, atime= ?, mtime= ?, mime_type = ? WHERE obj_id = ?"), //NON-NLS
6404 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_DERIVED_FILE);
6405 			statement.clearParameters();
6406 
6407 			//type
6408 			statement.setShort(1, TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.getFileType());
6409 
6410 			//flags
6411 			final TSK_FS_NAME_TYPE_ENUM dirType = isFile ? TSK_FS_NAME_TYPE_ENUM.REG : TSK_FS_NAME_TYPE_ENUM.DIR;
6412 			statement.setShort(2, dirType.getValue());
6413 			final TSK_FS_META_TYPE_ENUM metaType = isFile ? TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG : TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
6414 			statement.setShort(3, metaType.getValue());
6415 
6416 			//note: using alloc under assumption that derived files derive from alloc files
6417 			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
6418 			statement.setShort(4, dirFlag.getValue());
6419 			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
6420 					| TSK_FS_META_FLAG_ENUM.USED.getValue());
6421 			statement.setShort(5, metaFlags);
6422 
6423 			//size
6424 			//prevent negative size
6425 			long savedSize = size < 0 ? 0 : size;
6426 			statement.setLong(6, savedSize);
6427 
6428 			//mactimes
6429 			//long ctime, long crtime, long atime, long mtime,
6430 			statement.setLong(7, ctime);
6431 			statement.setLong(8, crtime);
6432 			statement.setLong(9, atime);
6433 			statement.setLong(10, mtime);
6434 			statement.setString(11, mimeType);
6435 			statement.setString(12, String.valueOf(derivedFile.getId()));
6436 			connection.executeUpdate(statement);
6437 
6438 			//add localPath
6439 			updateFilePath(connection, derivedFile.getId(), localPath, encodingType);
6440 
6441 			connection.commitTransaction();
6442 
6443 			long dataSourceObjId = getDataSourceObjectId(connection, parentId);
6444 			final String extension = extractExtension(derivedFile.getName());
6445 			return new DerivedFile(this, derivedFile.getId(), dataSourceObjId, derivedFile.getName(), dirType, metaType, dirFlag, metaFlags,
6446 					savedSize, ctime, crtime, atime, mtime, null, null, parentPath, localPath, parentId, null, encodingType, extension);
6447 		} catch (SQLException ex) {
6448 			connection.rollbackTransaction();
6449 			throw new TskCoreException("Failed to add derived file to case database", ex);
6450 		} finally {
6451 			closeResultSet(rs);
6452 			connection.close();
6453 			releaseSingleUserCaseWriteLock();
6454 		}
6455 	}
6456 
6457 	/**
6458 	 * Wraps the version of addLocalFile that takes a Transaction in a
6459 	 * transaction local to this method.
6460 	 *
6461 	 * @param fileName
6462 	 * @param localPath
6463 	 * @param size
6464 	 * @param ctime
6465 	 * @param crtime
6466 	 * @param atime
6467 	 * @param mtime
6468 	 * @param isFile
6469 	 * @param encodingType
6470 	 * @param parent
6471 	 *
6472 	 * @return
6473 	 *
6474 	 * @throws TskCoreException
6475 	 */
6476 	public LocalFile addLocalFile(String fileName, String localPath,
6477 			long size, long ctime, long crtime, long atime, long mtime,
6478 			boolean isFile, TskData.EncodingType encodingType,
6479 			AbstractFile parent) throws TskCoreException {
6480 
6481 		CaseDbTransaction localTrans = beginTransaction();
6482 		try {
6483 			LocalFile created = addLocalFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile, encodingType, parent, localTrans);
6484 			localTrans.commit();
6485 			localTrans = null;
6486 			return created;
6487 		} finally {
6488 			if (null != localTrans) {
6489 				try {
6490 					localTrans.rollback();
6491 				} catch (TskCoreException ex2) {
6492 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
6493 				}
6494 			}
6495 		}
6496 	}
6497 
6498 	/**
6499 	 * Adds a local/logical file to the case database. The database operations
6500 	 * are done within a caller-managed transaction; the caller is responsible
6501 	 * for committing or rolling back the transaction.
6502 	 *
6503 	 * @param fileName     The name of the file.
6504 	 * @param localPath    The absolute path (including the file name) of the
6505 	 *                     local/logical in secondary storage.
6506 	 * @param size         The size of the file in bytes.
6507 	 * @param ctime        The changed time of the file.
6508 	 * @param crtime       The creation time of the file.
6509 	 * @param atime        The accessed time of the file
6510 	 * @param mtime        The modified time of the file.
6511 	 * @param isFile       True, unless the file is a directory.
6512 	 * @param encodingType Type of encoding used on the file
6513 	 * @param parent       The parent of the file (e.g., a virtual directory)
6514 	 * @param transaction  A caller-managed transaction within which the add
6515 	 *                     file operations are performed.
6516 	 *
6517 	 * @return An object representing the local/logical file.
6518 	 *
6519 	 * @throws TskCoreException if there is an error completing a case database
6520 	 *                          operation.
6521 	 */
6522 	public LocalFile addLocalFile(String fileName, String localPath,
6523 			long size, long ctime, long crtime, long atime, long mtime,
6524 			boolean isFile, TskData.EncodingType encodingType,
6525 			Content parent, CaseDbTransaction transaction) throws TskCoreException {
6526 
6527 		return addLocalFile(fileName, localPath,
6528 				size, ctime, crtime, atime, mtime,
6529 				null, null, null,
6530 				isFile, encodingType,
6531 				parent, transaction);
6532 	}
6533 
6534 	/**
6535 	 * Adds a local/logical file to the case database. The database operations
6536 	 * are done within a caller-managed transaction; the caller is responsible
6537 	 * for committing or rolling back the transaction.
6538 	 *
6539 	 * @param fileName     The name of the file.
6540 	 * @param localPath    The absolute path (including the file name) of the
6541 	 *                     local/logical in secondary storage.
6542 	 * @param size         The size of the file in bytes.
6543 	 * @param ctime        The changed time of the file.
6544 	 * @param crtime       The creation time of the file.
6545 	 * @param atime        The accessed time of the file
6546 	 * @param mtime        The modified time of the file.
6547 	 * @param md5          The MD5 hash of the file
6548 	 * @param known        The known status of the file (can be null)
6549 	 * @param mimeType     The MIME type of the file
6550 	 * @param isFile       True, unless the file is a directory.
6551 	 * @param encodingType Type of encoding used on the file
6552 	 * @param parent       The parent of the file (e.g., a virtual directory)
6553 	 * @param transaction  A caller-managed transaction within which the add
6554 	 *                     file operations are performed.
6555 	 *
6556 	 * @return An object representing the local/logical file.
6557 	 *
6558 	 * @throws TskCoreException if there is an error completing a case database
6559 	 *                          operation.
6560 	 */
6561 	public LocalFile addLocalFile(String fileName, String localPath,
6562 			long size, long ctime, long crtime, long atime, long mtime,
6563 			String md5, FileKnown known, String mimeType,
6564 			boolean isFile, TskData.EncodingType encodingType,
6565 			Content parent, CaseDbTransaction transaction) throws TskCoreException {
6566 		CaseDbConnection connection = transaction.getConnection();
6567 		transaction.acquireSingleUserCaseWriteLock();
6568 		Statement queryStatement = null;
6569 		try {
6570 
6571 			// Insert a row for the local/logical file into the tsk_objects table.
6572 			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
6573 			long objectId = addObject(parent.getId(), TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
6574 
6575 			// Insert a row for the local/logical file into the tsk_files table.
6576 			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
6577 			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type,
6578 			// parent_path, data_source_obj_id,extension)
6579 			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
6580 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
6581 			statement.clearParameters();
6582 			statement.setLong(1, objectId);
6583 			statement.setNull(2, java.sql.Types.BIGINT); // Not part of a file system
6584 			statement.setString(3, fileName);
6585 			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.getFileType());
6586 			statement.setShort(5, (short) 1);
6587 			TSK_FS_NAME_TYPE_ENUM dirType = isFile ? TSK_FS_NAME_TYPE_ENUM.REG : TSK_FS_NAME_TYPE_ENUM.DIR;
6588 			statement.setShort(6, dirType.getValue());
6589 			TSK_FS_META_TYPE_ENUM metaType = isFile ? TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG : TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
6590 			statement.setShort(7, metaType.getValue());
6591 			TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
6592 			statement.setShort(8, dirFlag.getValue());
6593 			short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue() | TSK_FS_META_FLAG_ENUM.USED.getValue());
6594 			statement.setShort(9, metaFlags);
6595 			//prevent negative size
6596 			long savedSize = size < 0 ? 0 : size;
6597 			statement.setLong(10, savedSize);
6598 			statement.setLong(11, ctime);
6599 			statement.setLong(12, crtime);
6600 			statement.setLong(13, atime);
6601 			statement.setLong(14, mtime);
6602 			statement.setString(15, md5);
6603 			if (known != null) {
6604 				statement.setByte(16, known.getFileKnownValue());
6605 			} else {
6606 				statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue());
6607 			}
6608 			statement.setString(17, mimeType);
6609 			String parentPath;
6610 			long dataSourceObjId;
6611 
6612 			if (parent instanceof AbstractFile) {
6613 				AbstractFile parentFile = (AbstractFile) parent;
6614 				if (isRootDirectory(parentFile, transaction)) {
6615 					parentPath = "/";
6616 				} else {
6617 					parentPath = parentFile.getParentPath() + parent.getName() + "/"; //NON-NLS
6618 				}
6619 				dataSourceObjId = parentFile.getDataSourceObjectId();
6620 			} else {
6621 				parentPath = "/";
6622 				dataSourceObjId = getDataSourceObjectId(connection, parent.getId());
6623 			}
6624 			statement.setString(18, parentPath);
6625 			statement.setLong(19, dataSourceObjId);
6626 			final String extension = extractExtension(fileName);
6627 			statement.setString(20, extension);
6628 
6629 			connection.executeUpdate(statement);
6630 			addFilePath(connection, objectId, localPath, encodingType);
6631 			LocalFile localFile = new LocalFile(this,
6632 					objectId,
6633 					fileName,
6634 					TSK_DB_FILES_TYPE_ENUM.LOCAL,
6635 					dirType,
6636 					metaType,
6637 					dirFlag,
6638 					metaFlags,
6639 					savedSize,
6640 					ctime, crtime, atime, mtime,
6641 					mimeType, md5, known,
6642 					parent.getId(), parentPath,
6643 					dataSourceObjId,
6644 					localPath,
6645 					encodingType, extension);
6646 			getTimelineManager().addEventsForNewFile(localFile, connection);
6647 			return localFile;
6648 
6649 		} catch (SQLException ex) {
6650 			throw new TskCoreException(String.format("Failed to INSERT local file %s (%s) with parent id %d in tsk_files table", fileName, localPath, parent.getId()), ex);
6651 		} finally {
6652 			closeStatement(queryStatement);
6653 			// NOTE: write lock will be released by transaction
6654 		}
6655 	}
6656 
6657 	/**
6658 	 * Check whether a given AbstractFile is the "root" directory. True if the
6659 	 * AbstractFile either has no parent or its parent is an image, volume,
6660 	 * volume system, or file system.
6661 	 *
6662 	 * @param file        the file to test
6663 	 * @param transaction the current transaction
6664 	 *
6665 	 * @return true if the file is a root directory, false otherwise
6666 	 *
6667 	 * @throws TskCoreException
6668 	 */
6669 	private boolean isRootDirectory(AbstractFile file, CaseDbTransaction transaction) throws TskCoreException {
6670 		CaseDbConnection connection = transaction.getConnection();
6671 		transaction.acquireSingleUserCaseWriteLock();
6672 		Statement statement = null;
6673 		ResultSet resultSet = null;
6674 
6675 		try {
6676 			String query = String.format("SELECT ParentRow.type AS parent_type, ParentRow.obj_id AS parent_object_id "
6677 					+ "FROM tsk_objects ParentRow JOIN tsk_objects ChildRow ON ChildRow.par_obj_id = ParentRow.obj_id "
6678 					+ "WHERE ChildRow.obj_id = %s;", file.getId());
6679 
6680 			statement = connection.createStatement();
6681 			resultSet = statement.executeQuery(query);
6682 			if (resultSet.next()) {
6683 				long parentId = resultSet.getLong("parent_object_id");
6684 				if (parentId == 0) {
6685 					return true;
6686 				}
6687 				int type = resultSet.getInt("parent_type");
6688 				return (type == TskData.ObjectType.IMG.getObjectType()
6689 						|| type == TskData.ObjectType.VS.getObjectType()
6690 						|| type == TskData.ObjectType.VOL.getObjectType()
6691 						|| type == TskData.ObjectType.FS.getObjectType());
6692 
6693 			} else {
6694 				return true; // The file has no parent
6695 			}
6696 		} catch (SQLException ex) {
6697 			throw new TskCoreException(String.format("Failed to lookup parent of file (%s) with id %d", file.getName(), file.getId()), ex);
6698 		} finally {
6699 			closeResultSet(resultSet);
6700 			closeStatement(statement);
6701 			// NOTE: write lock will be released by transaction
6702 		}
6703 	}
6704 
6705 	/**
6706 	 * Add a new layout file to the database.
6707 	 *
6708 	 * @param fileName   The name of the file.
6709 	 * @param size       The size of the file in bytes.
6710 	 * @param dirFlag    The allocated status from the name structure
6711 	 * @param metaFlag   The allocated status from the metadata structure
6712 	 * @param ctime      The changed time of the file.
6713 	 * @param crtime     The creation time of the file.
6714 	 * @param atime      The accessed time of the file
6715 	 * @param mtime      The modified time of the file.
6716 	 * @param fileRanges The byte ranges that belong to this file (relative to
6717 	 *                   start of image)
6718 	 * @param parent     The parent of the file
6719 	 *
6720 	 * @return The new LayoutFile
6721 	 *
6722 	 * @throws TskCoreException
6723 	 */
6724 	public LayoutFile addLayoutFile(String fileName,
6725 			long size,
6726 			TSK_FS_NAME_FLAG_ENUM dirFlag, TSK_FS_META_FLAG_ENUM metaFlag,
6727 			long ctime, long crtime, long atime, long mtime,
6728 			List<TskFileRange> fileRanges,
6729 			Content parent) throws TskCoreException {
6730 
6731 		if (null == parent) {
6732 			throw new TskCoreException("Parent can not be null");
6733 		}
6734 
6735 		String parentPath;
6736 		if (parent instanceof AbstractFile) {
6737 			parentPath = ((AbstractFile) parent).getParentPath() + parent.getName() + '/'; //NON-NLS
6738 		} else {
6739 			parentPath = "/";
6740 		}
6741 
6742 		CaseDbTransaction transaction = null;
6743 		Statement statement = null;
6744 		ResultSet resultSet = null;
6745 		try {
6746 			transaction = beginTransaction();
6747 			transaction.acquireSingleUserCaseWriteLock();
6748 			CaseDbConnection connection = transaction.getConnection();
6749 
6750 			/*
6751 			 * Insert a row for the layout file into the tsk_objects table:
6752 			 * INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
6753 			 */
6754 			long newFileId = addObject(parent.getId(), TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
6755 
6756 			/*
6757 			 * Insert a row for the file into the tsk_files table: INSERT INTO
6758 			 * tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type,
6759 			 * meta_type, dir_flags, meta_flags, size, ctime, crtime, atime,
6760 			 * mtime, md5, known, mime_type, parent_path,
6761 			 * data_source_obj_id,extenion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
6762 			 * ?, ?, ?, ?, ?, ?, ?,?)
6763 			 */
6764 			PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
6765 			prepStmt.clearParameters();
6766 			prepStmt.setLong(1, newFileId); // obj_id
6767 
6768 			// If the parent is part of a file system, grab its file system ID
6769 			if (0 != parent.getId()) {
6770 				long parentFs = this.getFileSystemId(parent.getId(), connection);
6771 				if (parentFs != -1) {
6772 					prepStmt.setLong(2, parentFs);
6773 				} else {
6774 					prepStmt.setNull(2, java.sql.Types.BIGINT);
6775 				}
6776 			} else {
6777 				prepStmt.setNull(2, java.sql.Types.BIGINT);
6778 			}
6779 			prepStmt.setString(3, fileName); // name
6780 			prepStmt.setShort(4, TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.getFileType()); // type
6781 			prepStmt.setShort(5, (short) 0); // has_path
6782 			prepStmt.setShort(6, TSK_FS_NAME_TYPE_ENUM.REG.getValue()); // dir_type
6783 			prepStmt.setShort(7, TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue()); // meta_type
6784 			prepStmt.setShort(8, dirFlag.getValue()); // dir_flags
6785 			prepStmt.setShort(9, metaFlag.getValue()); // meta_flags
6786 			//prevent negative size
6787 			long savedSize = size < 0 ? 0 : size;
6788 			prepStmt.setLong(10, savedSize);   // size
6789 			prepStmt.setLong(11, ctime);  // ctime
6790 			prepStmt.setLong(12, crtime); // crtime
6791 			prepStmt.setLong(13, atime);  // atime
6792 			prepStmt.setLong(14, mtime);  // mtime
6793 			prepStmt.setNull(15, java.sql.Types.VARCHAR); // MD5
6794 			prepStmt.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
6795 			prepStmt.setNull(17, java.sql.Types.VARCHAR); // MIME type
6796 			prepStmt.setString(18, parentPath); // parent path
6797 			prepStmt.setLong(19, parent.getDataSource().getId()); // data_source_obj_id
6798 
6799 			prepStmt.setString(20, extractExtension(fileName)); 				//extension
6800 			connection.executeUpdate(prepStmt);
6801 
6802 			/*
6803 			 * Insert a row in the tsk_layout_file table for each chunk of the
6804 			 * carved file. INSERT INTO tsk_file_layout (obj_id, byte_start,
6805 			 * byte_len, sequence) VALUES (?, ?, ?, ?)
6806 			 */
6807 			prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LAYOUT_FILE);
6808 			for (TskFileRange tskFileRange : fileRanges) {
6809 				prepStmt.clearParameters();
6810 				prepStmt.setLong(1, newFileId); // obj_id
6811 				prepStmt.setLong(2, tskFileRange.getByteStart()); // byte_start
6812 				prepStmt.setLong(3, tskFileRange.getByteLen()); // byte_len
6813 				prepStmt.setLong(4, tskFileRange.getSequence()); // sequence
6814 				connection.executeUpdate(prepStmt);
6815 			}
6816 
6817 			/*
6818 			 * Create a layout file representation of the carved file.
6819 			 */
6820 			LayoutFile layoutFile = new LayoutFile(this,
6821 					newFileId,
6822 					parent.getDataSource().getId(),
6823 					fileName,
6824 					TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE,
6825 					TSK_FS_NAME_TYPE_ENUM.REG,
6826 					TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG,
6827 					dirFlag,
6828 					metaFlag.getValue(),
6829 					savedSize,
6830 					ctime, crtime, atime, mtime,
6831 					null,
6832 					FileKnown.UNKNOWN,
6833 					parentPath,
6834 					null);
6835 
6836 			transaction.commit();
6837 			transaction = null;
6838 			return layoutFile;
6839 
6840 		} catch (SQLException ex) {
6841 			throw new TskCoreException("Failed to add layout file " + fileName + " to case database", ex);
6842 		} finally {
6843 			closeResultSet(resultSet);
6844 			closeStatement(statement);
6845 
6846 			// NOTE: write lock will be released by transaction
6847 			if (null != transaction) {
6848 				try {
6849 					transaction.rollback();
6850 				} catch (TskCoreException ex2) {
6851 					logger.log(Level.SEVERE, "Failed to rollback transaction after exception", ex2);
6852 				}
6853 			}
6854 		}
6855 	}
6856 
6857 	/**
6858 	 * Given an object id, works up the tree of ancestors to the data source for
6859 	 * the object and gets the object id of the data source. The trivial case
6860 	 * where the input object id is for a source is handled.
6861 	 *
6862 	 * @param connection A case database connection.
6863 	 * @param objectId   An object id.
6864 	 *
6865 	 * @return A data source object id.
6866 	 *
6867 	 * @throws TskCoreException if there is an error querying the case database.
6868 	 */
6869 	private long getDataSourceObjectId(CaseDbConnection connection, long objectId) throws TskCoreException {
6870 		acquireSingleUserCaseReadLock();
6871 		Statement statement = null;
6872 		ResultSet resultSet = null;
6873 		try {
6874 			statement = connection.createStatement();
6875 			long dataSourceObjId;
6876 			long ancestorId = objectId;
6877 			do {
6878 				dataSourceObjId = ancestorId;
6879 				String query = String.format("SELECT par_obj_id FROM tsk_objects WHERE obj_id = %s;", ancestorId);
6880 				resultSet = statement.executeQuery(query);
6881 				if (resultSet.next()) {
6882 					ancestorId = resultSet.getLong("par_obj_id");
6883 				} else {
6884 					throw new TskCoreException(String.format("tsk_objects table is corrupt, SQL query returned no result: %s", query));
6885 				}
6886 				resultSet.close();
6887 				resultSet = null;
6888 			} while (0 != ancestorId); // Not NULL
6889 			return dataSourceObjId;
6890 		} catch (SQLException ex) {
6891 			throw new TskCoreException(String.format("Error finding root data source for object (obj_id = %d)", objectId), ex);
6892 		} finally {
6893 			closeResultSet(resultSet);
6894 			closeStatement(statement);
6895 			releaseSingleUserCaseReadLock();
6896 		}
6897 	}
6898 
6899 	/**
6900 	 * Add a path (such as a local path) for a content object to tsk_file_paths
6901 	 *
6902 	 * @param connection A case database connection.
6903 	 * @param objId      The object id of the file for which to add the path.
6904 	 * @param path       The path to add.
6905 	 * @param type       The TSK encoding type of the file.
6906 	 *
6907 	 * @throws SQLException Thrown if database error occurred and path was not
6908 	 *                      added.
6909 	 */
6910 	private void addFilePath(CaseDbConnection connection, long objId, String path, TskData.EncodingType type) throws SQLException {
6911 		PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LOCAL_PATH);
6912 		statement.clearParameters();
6913 		statement.setLong(1, objId);
6914 		statement.setString(2, path);
6915 		statement.setInt(3, type.getType());
6916 		connection.executeUpdate(statement);
6917 	}
6918 
6919 	/**
6920 	 * Update the path for a content object in the tsk_file_paths table
6921 	 *
6922 	 * @param connection A case database connection.
6923 	 * @param objId      The object id of the file for which to update the path.
6924 	 * @param path       The path to update.
6925 	 * @param type       The TSK encoding type of the file.
6926 	 *
6927 	 * @throws SQLException Thrown if database error occurred and path was not
6928 	 *                      updated.
6929 	 */
6930 	private void updateFilePath(CaseDbConnection connection, long objId, String path, TskData.EncodingType type) throws SQLException {
6931 		PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_LOCAL_PATH);
6932 		statement.clearParameters();
6933 		statement.setString(1, path);
6934 		statement.setInt(2, type.getType());
6935 		statement.setLong(3, objId);
6936 		connection.executeUpdate(statement);
6937 	}
6938 
6939 	/**
6940 	 * Find all files in the data source, by name and parent
6941 	 *
6942 	 * @param dataSource the dataSource (Image, parent-less VirtualDirectory) to
6943 	 *                   search for the given file name
6944 	 * @param fileName   Pattern of the name of the file or directory to match
6945 	 *                   (case insensitive, used in LIKE SQL statement).
6946 	 * @param parentFile Object for parent file/directory to find children in
6947 	 *
6948 	 * @return a list of AbstractFile for files/directories whose name matches
6949 	 *         fileName and that were inside a directory described by
6950 	 *         parentFile.
6951 	 *
6952 	 * @throws org.sleuthkit.datamodel.TskCoreException
6953 	 */
6954 	public List<AbstractFile> findFiles(Content dataSource, String fileName, AbstractFile parentFile) throws TskCoreException {
6955 		return findFiles(dataSource, fileName, parentFile.getName());
6956 	}
6957 
6958 	/**
6959 	 * Count files matching the specific Where clause
6960 	 *
6961 	 * @param sqlWhereClause a SQL where clause appropriate for the desired
6962 	 *                       files (do not begin the WHERE clause with the word
6963 	 *                       WHERE!)
6964 	 *
6965 	 * @return count of files each of which satisfy the given WHERE clause
6966 	 *
6967 	 * @throws TskCoreException \ref query_database_page
6968 	 */
6969 	public long countFilesWhere(String sqlWhereClause) throws TskCoreException {
6970 		CaseDbConnection connection = connections.getConnection();
6971 		acquireSingleUserCaseReadLock();
6972 		Statement s = null;
6973 		ResultSet rs = null;
6974 		try {
6975 			s = connection.createStatement();
6976 			rs = connection.executeQuery(s, "SELECT COUNT(*) AS count FROM tsk_files WHERE " + sqlWhereClause); //NON-NLS
6977 			rs.next();
6978 			return rs.getLong("count");
6979 		} catch (SQLException e) {
6980 			throw new TskCoreException("SQLException thrown when calling 'SleuthkitCase.countFilesWhere().", e);
6981 		} finally {
6982 			closeResultSet(rs);
6983 			closeStatement(s);
6984 			connection.close();
6985 			releaseSingleUserCaseReadLock();
6986 		}
6987 	}
6988 
6989 	/**
6990 	 * Find and return list of all (abstract) files matching the specific Where
6991 	 * clause. You need to know the database schema to use this, which is
6992 	 * outlined on the
6993 	 * <a href="http://wiki.sleuthkit.org/index.php?title=SQLite_Database_v3_Schema">wiki</a>.
6994 	 * You should use enums from org.sleuthkit.datamodel.TskData to make the
6995 	 * queries easier to maintain and understand.
6996 	 *
6997 	 * @param sqlWhereClause a SQL where clause appropriate for the desired
6998 	 *                       files (do not begin the WHERE clause with the word
6999 	 *                       WHERE!)
7000 	 *
7001 	 * @return a list of AbstractFile each of which satisfy the given WHERE
7002 	 *         clause
7003 	 *
7004 	 * @throws TskCoreException \ref query_database_page
7005 	 */
7006 	public List<AbstractFile> findAllFilesWhere(String sqlWhereClause) throws TskCoreException {
7007 		CaseDbConnection connection = connections.getConnection();
7008 		acquireSingleUserCaseReadLock();
7009 		Statement s = null;
7010 		ResultSet rs = null;
7011 		try {
7012 			s = connection.createStatement();
7013 			rs = connection.executeQuery(s, "SELECT * FROM tsk_files WHERE " + sqlWhereClause); //NON-NLS
7014 			return resultSetToAbstractFiles(rs, connection);
7015 		} catch (SQLException e) {
7016 			throw new TskCoreException("SQLException thrown when calling 'SleuthkitCase.findAllFilesWhere(): " + sqlWhereClause, e);
7017 		} finally {
7018 			closeResultSet(rs);
7019 			closeStatement(s);
7020 			connection.close();
7021 			releaseSingleUserCaseReadLock();
7022 		}
7023 	}
7024 
7025 	/**
7026 	 * Find and return list of all (abstract) ids of files matching the specific
7027 	 * Where clause
7028 	 *
7029 	 * @param sqlWhereClause a SQL where clause appropriate for the desired
7030 	 *                       files (do not begin the WHERE clause with the word
7031 	 *                       WHERE!)
7032 	 *
7033 	 * @return a list of file ids each of which satisfy the given WHERE clause
7034 	 *
7035 	 * @throws TskCoreException \ref query_database_page
7036 	 */
7037 	public List<Long> findAllFileIdsWhere(String sqlWhereClause) throws TskCoreException {
7038 		CaseDbConnection connection = connections.getConnection();
7039 		acquireSingleUserCaseReadLock();
7040 		Statement s = null;
7041 		ResultSet rs = null;
7042 		try {
7043 			s = connection.createStatement();
7044 			rs = connection.executeQuery(s, "SELECT obj_id FROM tsk_files WHERE " + sqlWhereClause); //NON-NLS
7045 			List<Long> ret = new ArrayList<Long>();
7046 			while (rs.next()) {
7047 				ret.add(rs.getLong("obj_id"));
7048 			}
7049 			return ret;
7050 		} catch (SQLException e) {
7051 			throw new TskCoreException("SQLException thrown when calling 'SleuthkitCase.findAllFileIdsWhere(): " + sqlWhereClause, e);
7052 		} finally {
7053 			closeResultSet(rs);
7054 			closeStatement(s);
7055 			connection.close();
7056 			releaseSingleUserCaseReadLock();
7057 		}
7058 	}
7059 
7060 	/**
7061 	 * @param dataSource the data source (Image, VirtualDirectory for file-sets,
7062 	 *                   etc) to search for the given file name
7063 	 * @param filePath   The full path to the file(s) of interest. This can
7064 	 *                   optionally include the image and volume names. Treated
7065 	 *                   in a case- insensitive manner.
7066 	 *
7067 	 * @return a list of AbstractFile that have the given file path.
7068 	 *
7069 	 * @throws org.sleuthkit.datamodel.TskCoreException
7070 	 */
7071 	public List<AbstractFile> openFiles(Content dataSource, String filePath) throws TskCoreException {
7072 
7073 		// get the non-unique path (strip of image and volume path segments, if
7074 		// the exist.
7075 		String path = AbstractFile.createNonUniquePath(filePath).toLowerCase();
7076 
7077 		// split the file name from the parent path
7078 		int lastSlash = path.lastIndexOf('/'); //NON-NLS
7079 
7080 		// if the last slash is at the end, strip it off
7081 		if (lastSlash == path.length()) {
7082 			path = path.substring(0, lastSlash - 1);
7083 			lastSlash = path.lastIndexOf('/'); //NON-NLS
7084 		}
7085 
7086 		String parentPath = path.substring(0, lastSlash);
7087 		String fileName = path.substring(lastSlash);
7088 
7089 		return findFiles(dataSource, fileName, parentPath);
7090 	}
7091 
7092 	/**
7093 	 * Get file layout ranges from tsk_file_layout, for a file with specified id
7094 	 *
7095 	 * @param id of the file to get file layout ranges for
7096 	 *
7097 	 * @return list of populated file ranges
7098 	 *
7099 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7100 	 *                          core
7101 	 */
7102 	public List<TskFileRange> getFileRanges(long id) throws TskCoreException {
7103 		CaseDbConnection connection = connections.getConnection();
7104 		acquireSingleUserCaseReadLock();
7105 		Statement s = null;
7106 		ResultSet rs = null;
7107 		try {
7108 			s = connection.createStatement();
7109 			rs = connection.executeQuery(s, "SELECT * FROM tsk_file_layout WHERE obj_id = " + id + " ORDER BY sequence");
7110 			List<TskFileRange> ranges = new ArrayList<TskFileRange>();
7111 			while (rs.next()) {
7112 				TskFileRange range = new TskFileRange(rs.getLong("byte_start"), //NON-NLS
7113 						rs.getLong("byte_len"), rs.getLong("sequence")); //NON-NLS
7114 				ranges.add(range);
7115 			}
7116 			return ranges;
7117 		} catch (SQLException ex) {
7118 			throw new TskCoreException("Error getting TskFileLayoutRanges by id, id = " + id, ex);
7119 		} finally {
7120 			closeResultSet(rs);
7121 			closeStatement(s);
7122 			connection.close();
7123 			releaseSingleUserCaseReadLock();
7124 		}
7125 	}
7126 
7127 	/**
7128 	 * Get am image by the image object id
7129 	 *
7130 	 * @param id of the image object
7131 	 *
7132 	 * @return Image object populated
7133 	 *
7134 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7135 	 *                          core
7136 	 */
7137 	public Image getImageById(long id) throws TskCoreException {
7138 		CaseDbConnection connection = connections.getConnection();
7139 		acquireSingleUserCaseReadLock();
7140 		Statement s1 = null;
7141 		ResultSet rs1 = null;
7142 		Statement s2 = null;
7143 		ResultSet rs2 = null;
7144 		try {
7145 			s1 = connection.createStatement();
7146 			rs1 = connection.executeQuery(s1, "SELECT tsk_image_info.type, tsk_image_info.ssize, tsk_image_info.tzone, tsk_image_info.size, tsk_image_info.md5, tsk_image_info.sha1, tsk_image_info.sha256, tsk_image_info.display_name, data_source_info.device_id "
7147 					+ "FROM tsk_image_info "
7148 					+ "INNER JOIN data_source_info ON tsk_image_info.obj_id = data_source_info.obj_id "
7149 					+ "WHERE tsk_image_info.obj_id = " + id); //NON-NLS
7150 			if (rs1.next()) {
7151 				s2 = connection.createStatement();
7152 				rs2 = connection.executeQuery(s2, "SELECT name FROM tsk_image_names WHERE tsk_image_names.obj_id = " + id); //NON-NLS
7153 				List<String> imagePaths = new ArrayList<String>();
7154 				while (rs2.next()) {
7155 					imagePaths.add(rs2.getString("name"));
7156 				}
7157 				long type = rs1.getLong("type"); //NON-NLS
7158 				long ssize = rs1.getLong("ssize"); //NON-NLS
7159 				String tzone = rs1.getString("tzone"); //NON-NLS
7160 				long size = rs1.getLong("size"); //NON-NLS
7161 				String md5 = rs1.getString("md5"); //NON-NLS
7162 				String sha1 = rs1.getString("sha1"); //NON-NLS
7163 				String sha256 = rs1.getString("sha256"); //NON-NLS
7164 				String name = rs1.getString("display_name");
7165 				if (name == null) {
7166 					if (imagePaths.size() > 0) {
7167 						String path = imagePaths.get(0);
7168 						name = (new java.io.File(path)).getName();
7169 					} else {
7170 						name = "";
7171 					}
7172 				}
7173 				String device_id = rs1.getString("device_id");
7174 
7175 				return new Image(this, id, type, device_id, ssize, name,
7176 						imagePaths.toArray(new String[imagePaths.size()]), tzone, md5, sha1, sha256, size);
7177 			} else {
7178 				throw new TskCoreException("No image found for id: " + id);
7179 			}
7180 		} catch (SQLException ex) {
7181 			throw new TskCoreException("Error getting Image by id, id = " + id, ex);
7182 		} finally {
7183 			closeResultSet(rs2);
7184 			closeStatement(s2);
7185 			closeResultSet(rs1);
7186 			closeStatement(s1);
7187 			connection.close();
7188 			releaseSingleUserCaseReadLock();
7189 		}
7190 	}
7191 
7192 	/**
7193 	 * Get a volume system by the volume system object id
7194 	 *
7195 	 * @param id     id of the volume system
7196 	 * @param parent image containing the volume system
7197 	 *
7198 	 * @return populated VolumeSystem object
7199 	 *
7200 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7201 	 *                          core
7202 	 */
7203 	VolumeSystem getVolumeSystemById(long id, Image parent) throws TskCoreException {
7204 		CaseDbConnection connection = connections.getConnection();
7205 		acquireSingleUserCaseReadLock();
7206 		Statement s = null;
7207 		ResultSet rs = null;
7208 		try {
7209 			s = connection.createStatement();
7210 			rs = connection.executeQuery(s, "SELECT * FROM tsk_vs_info " //NON-NLS
7211 					+ "where obj_id = " + id); //NON-NLS
7212 			if (rs.next()) {
7213 				long type = rs.getLong("vs_type"); //NON-NLS
7214 				long imgOffset = rs.getLong("img_offset"); //NON-NLS
7215 				long blockSize = rs.getLong("block_size"); //NON-NLS
7216 				VolumeSystem vs = new VolumeSystem(this, id, "", type, imgOffset, blockSize);
7217 				vs.setParent(parent);
7218 				return vs;
7219 			} else {
7220 				throw new TskCoreException("No volume system found for id:" + id);
7221 			}
7222 		} catch (SQLException ex) {
7223 			throw new TskCoreException("Error getting Volume System by ID.", ex);
7224 		} finally {
7225 			closeResultSet(rs);
7226 			closeStatement(s);
7227 			connection.close();
7228 			releaseSingleUserCaseReadLock();
7229 		}
7230 	}
7231 
7232 	/**
7233 	 * @param id       ID of the desired VolumeSystem
7234 	 * @param parentId ID of the VolumeSystem's parent
7235 	 *
7236 	 * @return the VolumeSystem with the given ID
7237 	 *
7238 	 * @throws TskCoreException
7239 	 */
7240 	VolumeSystem getVolumeSystemById(long id, long parentId) throws TskCoreException {
7241 		VolumeSystem vs = getVolumeSystemById(id, null);
7242 		vs.setParentId(parentId);
7243 		return vs;
7244 	}
7245 
7246 	/**
7247 	 * Get a file system by the object id
7248 	 *
7249 	 * @param id     of the filesystem
7250 	 * @param parent parent Image of the file system
7251 	 *
7252 	 * @return populated FileSystem object
7253 	 *
7254 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7255 	 *                          core
7256 	 */
7257 	FileSystem getFileSystemById(long id, Image parent) throws TskCoreException {
7258 		return getFileSystemByIdHelper(id, parent);
7259 	}
7260 
7261 	/**
7262 	 * @param id       ID of the desired FileSystem
7263 	 * @param parentId ID of the FileSystem's parent
7264 	 *
7265 	 * @return the desired FileSystem
7266 	 *
7267 	 * @throws TskCoreException
7268 	 */
7269 	FileSystem getFileSystemById(long id, long parentId) throws TskCoreException {
7270 		Volume vol = null;
7271 		FileSystem fs = getFileSystemById(id, vol);
7272 		fs.setParentId(parentId);
7273 		return fs;
7274 	}
7275 
7276 	/**
7277 	 * Get a file system by the object id
7278 	 *
7279 	 * @param id     of the filesystem
7280 	 * @param parent parent Volume of the file system
7281 	 *
7282 	 * @return populated FileSystem object
7283 	 *
7284 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7285 	 *                          core
7286 	 */
7287 	FileSystem getFileSystemById(long id, Volume parent) throws TskCoreException {
7288 		return getFileSystemByIdHelper(id, parent);
7289 	}
7290 
7291 	/**
7292 	 * Get file system by id and Content parent
7293 	 *
7294 	 * @param id     of the filesystem to get
7295 	 * @param parent a direct parent Content object
7296 	 *
7297 	 * @return populated FileSystem object
7298 	 *
7299 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7300 	 *                          core
7301 	 */
7302 	private FileSystem getFileSystemByIdHelper(long id, Content parent) throws TskCoreException {
7303 		// see if we already have it
7304 		// @@@ NOTE: this is currently kind of bad in that we are ignoring the parent value,
7305 		// but it should be the same...
7306 		synchronized (fileSystemIdMap) {
7307 			if (fileSystemIdMap.containsKey(id)) {
7308 				return fileSystemIdMap.get(id);
7309 			}
7310 		}
7311 		CaseDbConnection connection = connections.getConnection();
7312 		acquireSingleUserCaseReadLock();
7313 		Statement s = null;
7314 		ResultSet rs = null;
7315 		try {
7316 			s = connection.createStatement();
7317 			rs = connection.executeQuery(s, "SELECT * FROM tsk_fs_info " //NON-NLS
7318 					+ "where obj_id = " + id); //NON-NLS
7319 			if (rs.next()) {
7320 				TskData.TSK_FS_TYPE_ENUM fsType = TskData.TSK_FS_TYPE_ENUM.valueOf(rs.getInt("fs_type")); //NON-NLS
7321 				FileSystem fs = new FileSystem(this, rs.getLong("obj_id"), "", rs.getLong("img_offset"), //NON-NLS
7322 						fsType, rs.getLong("block_size"), rs.getLong("block_count"), //NON-NLS
7323 						rs.getLong("root_inum"), rs.getLong("first_inum"), rs.getLong("last_inum")); //NON-NLS
7324 				fs.setParent(parent);
7325 				// save it for the next call
7326 				synchronized (fileSystemIdMap) {
7327 					fileSystemIdMap.put(id, fs);
7328 				}
7329 				return fs;
7330 			} else {
7331 				throw new TskCoreException("No file system found for id:" + id);
7332 			}
7333 		} catch (SQLException ex) {
7334 			throw new TskCoreException("Error getting File System by ID", ex);
7335 		} finally {
7336 			closeResultSet(rs);
7337 			closeStatement(s);
7338 			connection.close();
7339 			releaseSingleUserCaseReadLock();
7340 		}
7341 	}
7342 
7343 	/**
7344 	 * Get volume by id
7345 	 *
7346 	 * @param id
7347 	 * @param parent volume system
7348 	 *
7349 	 * @return populated Volume object
7350 	 *
7351 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7352 	 *                          core
7353 	 */
7354 	Volume getVolumeById(long id, VolumeSystem parent) throws TskCoreException {
7355 		CaseDbConnection connection = connections.getConnection();
7356 		acquireSingleUserCaseReadLock();
7357 		Statement s = null;
7358 		ResultSet rs = null;
7359 		try {
7360 			s = connection.createStatement();
7361 			rs = connection.executeQuery(s, "SELECT * FROM tsk_vs_parts " //NON-NLS
7362 					+ "where obj_id = " + id); //NON-NLS
7363 			if (rs.next()) {
7364 				/**
7365 				 * TODO!! LANDMINE!! This allows the two types of databases to
7366 				 * have slightly different schemas. SQLite uses desc as the
7367 				 * column name in tsk_vs_parts and Postgres uses descr, as desc
7368 				 * is a reserved keyword in Postgres. When we have to make a
7369 				 * schema change, be sure to change this over to just one name.
7370 				 */
7371 				String description;
7372 				try {
7373 					description = rs.getString("desc");
7374 				} catch (Exception ex) {
7375 					description = rs.getString("descr");
7376 				}
7377 				Volume vol = new Volume(this, rs.getLong("obj_id"), rs.getLong("addr"), //NON-NLS
7378 						rs.getLong("start"), rs.getLong("length"), rs.getLong("flags"), //NON-NLS
7379 						description);
7380 				vol.setParent(parent);
7381 				return vol;
7382 			} else {
7383 				throw new TskCoreException("No volume found for id:" + id);
7384 			}
7385 		} catch (SQLException ex) {
7386 			throw new TskCoreException("Error getting Volume by ID", ex);
7387 		} finally {
7388 			closeResultSet(rs);
7389 			closeStatement(s);
7390 			connection.close();
7391 			releaseSingleUserCaseReadLock();
7392 		}
7393 	}
7394 
7395 	/**
7396 	 * @param id       ID of the desired Volume
7397 	 * @param parentId ID of the Volume's parent
7398 	 *
7399 	 * @return the desired Volume
7400 	 *
7401 	 * @throws TskCoreException
7402 	 */
7403 	Volume getVolumeById(long id, long parentId) throws TskCoreException {
7404 		Volume vol = getVolumeById(id, null);
7405 		vol.setParentId(parentId);
7406 		return vol;
7407 	}
7408 
7409 	/**
7410 	 * Get a directory by id
7411 	 *
7412 	 * @param id       of the directory object
7413 	 * @param parentFs parent file system
7414 	 *
7415 	 * @return populated Directory object
7416 	 *
7417 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7418 	 *                          core
7419 	 */
7420 	Directory getDirectoryById(long id, FileSystem parentFs) throws TskCoreException {
7421 		CaseDbConnection connection = connections.getConnection();
7422 		acquireSingleUserCaseReadLock();
7423 		Statement s = null;
7424 		ResultSet rs = null;
7425 		try {
7426 			s = connection.createStatement();
7427 			rs = connection.executeQuery(s, "SELECT * FROM tsk_files " //NON-NLS
7428 					+ "WHERE obj_id = " + id);
7429 			Directory temp = null; //NON-NLS
7430 			if (rs.next()) {
7431 				final short type = rs.getShort("type"); //NON-NLS
7432 				if (type == TSK_DB_FILES_TYPE_ENUM.FS.getFileType()) {
7433 					if (rs.getShort("meta_type") == TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()
7434 							|| rs.getShort("meta_type") == TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR.getValue()) { //NON-NLS
7435 						temp = directory(rs, parentFs);
7436 					}
7437 				} else if (type == TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()) {
7438 					throw new TskCoreException("Expecting an FS-type directory, got virtual, id: " + id);
7439 				}
7440 			} else {
7441 				throw new TskCoreException("No Directory found for id:" + id);
7442 			}
7443 			return temp;
7444 		} catch (SQLException ex) {
7445 			throw new TskCoreException("Error getting Directory by ID", ex);
7446 		} finally {
7447 			closeResultSet(rs);
7448 			closeStatement(s);
7449 			connection.close();
7450 			releaseSingleUserCaseReadLock();
7451 		}
7452 	}
7453 
7454 	/**
7455 	 * Helper to return FileSystems in an Image
7456 	 *
7457 	 * @param image Image to lookup FileSystem for
7458 	 *
7459 	 * @return Collection of FileSystems in the image
7460 	 */
7461 	public Collection<FileSystem> getFileSystems(Image image) {
7462 		List<FileSystem> fileSystems = new ArrayList<FileSystem>();
7463 		CaseDbConnection connection;
7464 		try {
7465 			connection = connections.getConnection();
7466 		} catch (TskCoreException ex) {
7467 			logger.log(Level.SEVERE, "Error getting file systems for image " + image.getId(), ex); //NON-NLS
7468 			return fileSystems;
7469 		}
7470 		acquireSingleUserCaseReadLock();
7471 		Statement s = null;
7472 		ResultSet rs = null;
7473 		try {
7474 			s = connection.createStatement();
7475 
7476 			// Get all the file systems.
7477 			List<FileSystem> allFileSystems = new ArrayList<FileSystem>();
7478 			try {
7479 				rs = connection.executeQuery(s, "SELECT * FROM tsk_fs_info"); //NON-NLS
7480 				while (rs.next()) {
7481 					TskData.TSK_FS_TYPE_ENUM fsType = TskData.TSK_FS_TYPE_ENUM.valueOf(rs.getInt("fs_type")); //NON-NLS
7482 					FileSystem fs = new FileSystem(this, rs.getLong("obj_id"), "", rs.getLong("img_offset"), //NON-NLS
7483 							fsType, rs.getLong("block_size"), rs.getLong("block_count"), //NON-NLS
7484 							rs.getLong("root_inum"), rs.getLong("first_inum"), rs.getLong("last_inum")); //NON-NLS
7485 					fs.setParent(null);
7486 					allFileSystems.add(fs);
7487 				}
7488 			} catch (SQLException ex) {
7489 				logger.log(Level.SEVERE, "There was a problem while trying to obtain all file systems", ex); //NON-NLS
7490 			} finally {
7491 				closeResultSet(rs);
7492 				rs = null;
7493 			}
7494 
7495 			// For each file system, find the image to which it belongs by iteratively
7496 			// climbing the tsk_ojbects hierarchy only taking those file systems
7497 			// that belong to this image.
7498 			for (FileSystem fs : allFileSystems) {
7499 				Long imageID = null;
7500 				Long currentObjID = fs.getId();
7501 				while (imageID == null) {
7502 					try {
7503 						rs = connection.executeQuery(s, "SELECT * FROM tsk_objects WHERE tsk_objects.obj_id = " + currentObjID); //NON-NLS
7504 						rs.next();
7505 						currentObjID = rs.getLong("par_obj_id"); //NON-NLS
7506 						if (rs.getInt("type") == TskData.ObjectType.IMG.getObjectType()) { //NON-NLS
7507 							imageID = rs.getLong("obj_id"); //NON-NLS
7508 						}
7509 					} catch (SQLException ex) {
7510 						logger.log(Level.SEVERE, "There was a problem while trying to obtain this image's file systems", ex); //NON-NLS
7511 					} finally {
7512 						closeResultSet(rs);
7513 						rs = null;
7514 					}
7515 				}
7516 
7517 				// see if imageID is this image's ID
7518 				if (imageID == image.getId()) {
7519 					fileSystems.add(fs);
7520 				}
7521 			}
7522 		} catch (SQLException ex) {
7523 			logger.log(Level.SEVERE, "Error getting case database connection", ex); //NON-NLS
7524 		} finally {
7525 			closeResultSet(rs);
7526 			closeStatement(s);
7527 			connection.close();
7528 			releaseSingleUserCaseReadLock();
7529 		}
7530 		return fileSystems;
7531 	}
7532 
7533 	/**
7534 	 * Returns the list of direct children for a given Image
7535 	 *
7536 	 * @param img image to get children for
7537 	 *
7538 	 * @return list of Contents (direct image children)
7539 	 *
7540 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7541 	 *                          core
7542 	 */
7543 	List<Content> getImageChildren(Image img) throws TskCoreException {
7544 		Collection<ObjectInfo> childInfos = getChildrenInfo(img);
7545 		List<Content> children = new ArrayList<Content>();
7546 		for (ObjectInfo info : childInfos) {
7547 			if (null != info.type) {
7548 				switch (info.type) {
7549 					case VS:
7550 						children.add(getVolumeSystemById(info.id, img));
7551 						break;
7552 					case FS:
7553 						children.add(getFileSystemById(info.id, img));
7554 						break;
7555 					case ABSTRACTFILE:
7556 						AbstractFile f = getAbstractFileById(info.id);
7557 						if (f != null) {
7558 							children.add(f);
7559 						}
7560 						break;
7561 					case ARTIFACT:
7562 						BlackboardArtifact art = getArtifactById(info.id);
7563 						if (art != null) {
7564 							children.add(art);
7565 						}
7566 						break;
7567 					case REPORT:
7568 						// Do nothing for now - see JIRA-3673
7569 						break;
7570 					default:
7571 						throw new TskCoreException("Image has child of invalid type: " + info.type);
7572 				}
7573 			}
7574 		}
7575 		return children;
7576 	}
7577 
7578 	/**
7579 	 * Returns the list of direct children IDs for a given Image
7580 	 *
7581 	 * @param img image to get children for
7582 	 *
7583 	 * @return list of IDs (direct image children)
7584 	 *
7585 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7586 	 *                          core
7587 	 */
7588 	List<Long> getImageChildrenIds(Image img) throws TskCoreException {
7589 		Collection<ObjectInfo> childInfos = getChildrenInfo(img);
7590 		List<Long> children = new ArrayList<Long>();
7591 		for (ObjectInfo info : childInfos) {
7592 			if (info.type == ObjectType.VS
7593 					|| info.type == ObjectType.FS
7594 					|| info.type == ObjectType.ABSTRACTFILE
7595 					|| info.type == ObjectType.ARTIFACT) {
7596 				children.add(info.id);
7597 			} else if (info.type == ObjectType.REPORT) {
7598 				// Do nothing for now - see JIRA-3673
7599 			} else {
7600 				throw new TskCoreException("Image has child of invalid type: " + info.type);
7601 			}
7602 		}
7603 		return children;
7604 	}
7605 
7606 	/**
7607 	 * Returns the list of direct children for a given VolumeSystem
7608 	 *
7609 	 * @param vs volume system to get children for
7610 	 *
7611 	 * @return list of volume system children objects
7612 	 *
7613 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7614 	 *                          core
7615 	 */
7616 	List<Content> getVolumeSystemChildren(VolumeSystem vs) throws TskCoreException {
7617 		Collection<ObjectInfo> childInfos = getChildrenInfo(vs);
7618 		List<Content> children = new ArrayList<Content>();
7619 		for (ObjectInfo info : childInfos) {
7620 			if (null != info.type) {
7621 				switch (info.type) {
7622 					case VOL:
7623 						children.add(getVolumeById(info.id, vs));
7624 						break;
7625 					case ABSTRACTFILE:
7626 						AbstractFile f = getAbstractFileById(info.id);
7627 						if (f != null) {
7628 							children.add(f);
7629 						}
7630 						break;
7631 					case ARTIFACT:
7632 						BlackboardArtifact art = getArtifactById(info.id);
7633 						if (art != null) {
7634 							children.add(art);
7635 						}
7636 						break;
7637 					default:
7638 						throw new TskCoreException("VolumeSystem has child of invalid type: " + info.type);
7639 				}
7640 			}
7641 		}
7642 		return children;
7643 	}
7644 
7645 	/**
7646 	 * Returns the list of direct children IDs for a given VolumeSystem
7647 	 *
7648 	 * @param vs volume system to get children for
7649 	 *
7650 	 * @return list of volume system children IDs
7651 	 *
7652 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7653 	 *                          core
7654 	 */
7655 	List<Long> getVolumeSystemChildrenIds(VolumeSystem vs) throws TskCoreException {
7656 		Collection<ObjectInfo> childInfos = getChildrenInfo(vs);
7657 		List<Long> children = new ArrayList<Long>();
7658 		for (ObjectInfo info : childInfos) {
7659 			if (info.type == ObjectType.VOL || info.type == ObjectType.ABSTRACTFILE || info.type == ObjectType.ARTIFACT) {
7660 				children.add(info.id);
7661 			} else {
7662 				throw new TskCoreException("VolumeSystem has child of invalid type: " + info.type);
7663 			}
7664 		}
7665 		return children;
7666 	}
7667 
7668 	/**
7669 	 * Returns a list of direct children for a given Volume
7670 	 *
7671 	 * @param vol volume to get children of
7672 	 *
7673 	 * @return list of Volume children
7674 	 *
7675 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7676 	 *                          core
7677 	 */
7678 	List<Content> getVolumeChildren(Volume vol) throws TskCoreException {
7679 		Collection<ObjectInfo> childInfos = getChildrenInfo(vol);
7680 		List<Content> children = new ArrayList<Content>();
7681 		for (ObjectInfo info : childInfos) {
7682 			if (null != info.type) {
7683 				switch (info.type) {
7684 					case FS:
7685 						children.add(getFileSystemById(info.id, vol));
7686 						break;
7687 					case ABSTRACTFILE:
7688 						AbstractFile f = getAbstractFileById(info.id);
7689 						if (f != null) {
7690 							children.add(f);
7691 						}
7692 						break;
7693 					case ARTIFACT:
7694 						BlackboardArtifact art = getArtifactById(info.id);
7695 						if (art != null) {
7696 							children.add(art);
7697 						}
7698 						break;
7699 					default:
7700 						throw new TskCoreException("Volume has child of invalid type: " + info.type);
7701 				}
7702 			}
7703 		}
7704 		return children;
7705 	}
7706 
7707 	/**
7708 	 * Returns a list of direct children IDs for a given Volume
7709 	 *
7710 	 * @param vol volume to get children of
7711 	 *
7712 	 * @return list of Volume children IDs
7713 	 *
7714 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7715 	 *                          core
7716 	 */
7717 	List<Long> getVolumeChildrenIds(Volume vol) throws TskCoreException {
7718 		final Collection<ObjectInfo> childInfos = getChildrenInfo(vol);
7719 		final List<Long> children = new ArrayList<Long>();
7720 		for (ObjectInfo info : childInfos) {
7721 			if (info.type == ObjectType.FS || info.type == ObjectType.ABSTRACTFILE || info.type == ObjectType.ARTIFACT) {
7722 				children.add(info.id);
7723 			} else {
7724 				throw new TskCoreException("Volume has child of invalid type: " + info.type);
7725 			}
7726 		}
7727 		return children;
7728 	}
7729 
7730 	/**
7731 	 * Adds an image to the case database.
7732 	 *
7733 	 * @param deviceObjId    The object id of the device associated with the
7734 	 *                       image.
7735 	 * @param imageFilePaths The image file paths.
7736 	 * @param timeZone       The time zone for the image.
7737 	 *
7738 	 * @return An Image object.
7739 	 *
7740 	 * @throws TskCoreException if there is an error adding the image to case
7741 	 *                          database.
7742 	 */
7743 	public Image addImageInfo(long deviceObjId, List<String> imageFilePaths, String timeZone) throws TskCoreException {
7744 		long imageId = this.caseHandle.addImageInfo(deviceObjId, imageFilePaths, timeZone, this);
7745 		return getImageById(imageId);
7746 	}
7747 
7748 	/**
7749 	 * Returns a map of image object IDs to a list of fully qualified file paths
7750 	 * for that image
7751 	 *
7752 	 * @return map of image object IDs to file paths
7753 	 *
7754 	 * @throws TskCoreException thrown if a critical error occurred within tsk
7755 	 *                          core
7756 	 */
7757 	public Map<Long, List<String>> getImagePaths() throws TskCoreException {
7758 		CaseDbConnection connection = connections.getConnection();
7759 		acquireSingleUserCaseReadLock();
7760 		Statement s1 = null;
7761 		Statement s2 = null;
7762 		ResultSet rs1 = null;
7763 		ResultSet rs2 = null;
7764 		try {
7765 			s1 = connection.createStatement();
7766 			rs1 = connection.executeQuery(s1, "SELECT obj_id FROM tsk_image_info"); //NON-NLS
7767 			s2 = connection.createStatement();
7768 			Map<Long, List<String>> imgPaths = new LinkedHashMap<Long, List<String>>();
7769 			while (rs1.next()) {
7770 				long obj_id = rs1.getLong("obj_id"); //NON-NLS
7771 				rs2 = connection.executeQuery(s2, "SELECT * FROM tsk_image_names WHERE obj_id = " + obj_id); //NON-NLS
7772 				List<String> paths = new ArrayList<String>();
7773 				while (rs2.next()) {
7774 					paths.add(rs2.getString("name"));
7775 				}
7776 				rs2.close();
7777 				rs2 = null;
7778 				imgPaths.put(obj_id, paths);
7779 			}
7780 			return imgPaths;
7781 		} catch (SQLException ex) {
7782 			throw new TskCoreException("Error getting image paths.", ex);
7783 		} finally {
7784 			closeResultSet(rs2);
7785 			closeStatement(s2);
7786 			closeResultSet(rs1);
7787 			closeStatement(s1);
7788 			connection.close();
7789 			releaseSingleUserCaseReadLock();
7790 		}
7791 	}
7792 
7793 	/**
7794 	 * Returns a list of fully qualified file paths based on an image object ID.
7795 	 *
7796 	 * @param objectId The object id of the data source.
7797 	 *
7798 	 * @return List of file paths.
7799 	 *
7800 	 * @throws TskCoreException Thrown if a critical error occurred within tsk
7801 	 *                          core
7802 	 */
7803 	private List<String> getImagePathsById(long objectId) throws TskCoreException {
7804 		List<String> imagePaths = new ArrayList<String>();
7805 		CaseDbConnection connection = connections.getConnection();
7806 		acquireSingleUserCaseReadLock();
7807 		Statement statement = null;
7808 		ResultSet resultSet = null;
7809 		try {
7810 			statement = connection.createStatement();
7811 			resultSet = connection.executeQuery(statement, "SELECT name FROM tsk_image_names WHERE tsk_image_names.obj_id = " + objectId); //NON-NLS
7812 			while (resultSet.next()) {
7813 				imagePaths.add(resultSet.getString("name"));
7814 			}
7815 		} catch (SQLException ex) {
7816 			throw new TskCoreException(String.format("Error getting image names with obj_id = %d", objectId), ex);
7817 		} finally {
7818 			closeResultSet(resultSet);
7819 			closeStatement(statement);
7820 			connection.close();
7821 			releaseSingleUserCaseReadLock();
7822 		}
7823 
7824 		return imagePaths;
7825 	}
7826 
7827 	/**
7828 	 * @return a collection of Images associated with this instance of
7829 	 *         SleuthkitCase
7830 	 *
7831 	 * @throws TskCoreException
7832 	 */
7833 	public List<Image> getImages() throws TskCoreException {
7834 		CaseDbConnection connection = connections.getConnection();
7835 		acquireSingleUserCaseReadLock();
7836 		Statement s = null;
7837 		ResultSet rs = null;
7838 		try {
7839 			s = connection.createStatement();
7840 			rs = connection.executeQuery(s, "SELECT obj_id FROM tsk_image_info"); //NON-NLS
7841 			Collection<Long> imageIDs = new ArrayList<Long>();
7842 			while (rs.next()) {
7843 				imageIDs.add(rs.getLong("obj_id")); //NON-NLS
7844 			}
7845 			List<Image> images = new ArrayList<Image>();
7846 			for (long id : imageIDs) {
7847 				images.add(getImageById(id));
7848 			}
7849 			return images;
7850 		} catch (SQLException ex) {
7851 			throw new TskCoreException("Error retrieving images.", ex);
7852 		} finally {
7853 			closeResultSet(rs);
7854 			closeStatement(s);
7855 			connection.close();
7856 			releaseSingleUserCaseReadLock();
7857 		}
7858 	}
7859 
7860 	/**
7861 	 * Set the file paths for the image given by obj_id
7862 	 *
7863 	 * @param obj_id the ID of the image to update
7864 	 * @param paths  the fully qualified path to the files that make up the
7865 	 *               image
7866 	 *
7867 	 * @throws TskCoreException exception thrown when critical error occurs
7868 	 *                          within tsk core and the update fails
7869 	 */
7870 	public void setImagePaths(long obj_id, List<String> paths) throws TskCoreException {
7871 		CaseDbConnection connection = connections.getConnection();
7872 		acquireSingleUserCaseWriteLock();
7873 		PreparedStatement statement = null;
7874 		try {
7875 			connection.beginTransaction();
7876 			statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_IMAGE_NAME);
7877 			statement.clearParameters();
7878 			statement.setLong(1, obj_id);
7879 			connection.executeUpdate(statement);
7880 			for (int i = 0; i < paths.size(); i++) {
7881 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_IMAGE_NAME);
7882 				statement.clearParameters();
7883 				statement.setLong(1, obj_id);
7884 				statement.setString(2, paths.get(i));
7885 				statement.setLong(3, i);
7886 				connection.executeUpdate(statement);
7887 			}
7888 			connection.commitTransaction();
7889 		} catch (SQLException ex) {
7890 			connection.rollbackTransaction();
7891 			throw new TskCoreException("Error updating image paths.", ex);
7892 		} finally {
7893 			connection.close();
7894 			releaseSingleUserCaseWriteLock();
7895 		}
7896 	}
7897 
7898 	/**
7899 	 * Creates file object from a SQL query result set of rows from the
7900 	 * tsk_files table. Assumes that the query was of the form "SELECT * FROM
7901 	 * tsk_files WHERE XYZ".
7902 	 *
7903 	 * @param rs ResultSet to get content from. Caller is responsible for
7904 	 *           closing it.
7905 	 *
7906 	 * @return list of file objects from tsk_files table containing the files
7907 	 *
7908 	 * @throws SQLException if the query fails
7909 	 */
7910 	/**
7911 	 * Creates AbstractFile objects for the result set of a tsk_files table
7912 	 * query of the form "SELECT * FROM tsk_files WHERE XYZ".
7913 	 *
7914 	 * @param rs         A result set from a query of the tsk_files table of the
7915 	 *                   form "SELECT * FROM tsk_files WHERE XYZ".
7916 	 * @param connection A case database connection.
7917 	 *
7918 	 * @return A list of AbstractFile objects.
7919 	 *
7920 	 * @throws SQLException Thrown if there is a problem iterating through the
7921 	 *                      record set.
7922 	 */
7923 	private List<AbstractFile> resultSetToAbstractFiles(ResultSet rs, CaseDbConnection connection) throws SQLException {
7924 		ArrayList<AbstractFile> results = new ArrayList<AbstractFile>();
7925 		try {
7926 			while (rs.next()) {
7927 				final short type = rs.getShort("type"); //NON-NLS
7928 				if (type == TSK_DB_FILES_TYPE_ENUM.FS.getFileType()
7929 						&& (rs.getShort("meta_type") != TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR.getValue())) {
7930 					FsContent result;
7931 					if (rs.getShort("meta_type") == TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()) { //NON-NLS
7932 						result = directory(rs, null);
7933 					} else {
7934 						result = file(rs, null);
7935 					}
7936 					results.add(result);
7937 				} else if (type == TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
7938 						|| (rs.getShort("meta_type") == TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR.getValue())) { //NON-NLS
7939 					final VirtualDirectory virtDir = virtualDirectory(rs, connection);
7940 					results.add(virtDir);
7941 				} else if (type == TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR.getFileType()) {
7942 					final LocalDirectory localDir = localDirectory(rs);
7943 					results.add(localDir);
7944 				} else if (type == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType()
7945 						|| type == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS.getFileType()
7946 						|| type == TSK_DB_FILES_TYPE_ENUM.CARVED.getFileType()
7947 						|| type == TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.getFileType()) {
7948 					TSK_DB_FILES_TYPE_ENUM atype = TSK_DB_FILES_TYPE_ENUM.valueOf(type);
7949 					String parentPath = rs.getString("parent_path"); //NON-NLS
7950 					if (parentPath == null) {
7951 						parentPath = "/"; //NON-NLS
7952 					}
7953 					LayoutFile lf = new LayoutFile(this,
7954 							rs.getLong("obj_id"), //NON-NLS
7955 							rs.getLong("data_source_obj_id"),
7956 							rs.getString("name"), //NON-NLS
7957 							atype,
7958 							TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
7959 							TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), rs.getShort("meta_flags"), //NON-NLS
7960 							rs.getLong("size"), //NON-NLS
7961 							rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
7962 							rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), parentPath, rs.getString("mime_type")); //NON-NLS
7963 					results.add(lf);
7964 				} else if (type == TSK_DB_FILES_TYPE_ENUM.DERIVED.getFileType()) {
7965 					final DerivedFile df;
7966 					df = derivedFile(rs, connection, AbstractContent.UNKNOWN_ID);
7967 					results.add(df);
7968 				} else if (type == TSK_DB_FILES_TYPE_ENUM.LOCAL.getFileType()) {
7969 					final LocalFile lf;
7970 					lf = localFile(rs, connection, AbstractContent.UNKNOWN_ID);
7971 					results.add(lf);
7972 				} else if (type == TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType()) {
7973 					final SlackFile sf = slackFile(rs, null);
7974 					results.add(sf);
7975 				}
7976 			} //end for each resultSet
7977 		} catch (SQLException e) {
7978 			logger.log(Level.SEVERE, "Error getting abstract files from result set", e); //NON-NLS
7979 		}
7980 
7981 		return results;
7982 	}
7983 
7984 	// This following methods generate AbstractFile objects from a ResultSet
7985 	/**
7986 	 * Create a File object from the result set containing query results on
7987 	 * tsk_files table
7988 	 *
7989 	 * @param rs the result set
7990 	 * @param fs parent file system
7991 	 *
7992 	 * @return a newly create File
7993 	 *
7994 	 * @throws SQLException
7995 	 */
7996 	org.sleuthkit.datamodel.File file(ResultSet rs, FileSystem fs) throws SQLException {
7997 		org.sleuthkit.datamodel.File f = new org.sleuthkit.datamodel.File(this, rs.getLong("obj_id"), //NON-NLS
7998 				rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS
7999 				TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS
8000 				rs.getInt("attr_id"), rs.getString("name"), rs.getLong("meta_addr"), rs.getInt("meta_seq"), //NON-NLS
8001 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8002 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8003 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), //NON-NLS
8004 				rs.getShort("meta_flags"), rs.getLong("size"), //NON-NLS
8005 				rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
8006 				(short) rs.getInt("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS
8007 				rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS
8008 				rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension")); //NON-NLS
8009 		f.setFileSystem(fs);
8010 		return f;
8011 	}
8012 
8013 	/**
8014 	 * Create a Directory object from the result set containing query results on
8015 	 * tsk_files table
8016 	 *
8017 	 * @param rs the result set
8018 	 * @param fs parent file system
8019 	 *
8020 	 * @return a newly created Directory object
8021 	 *
8022 	 * @throws SQLException thrown if SQL error occurred
8023 	 */
8024 	Directory directory(ResultSet rs, FileSystem fs) throws SQLException {
8025 		Directory dir = new Directory(this, rs.getLong("obj_id"), rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS
8026 				TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS
8027 				rs.getInt("attr_id"), rs.getString("name"), rs.getLong("meta_addr"), rs.getInt("meta_seq"), //NON-NLS
8028 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8029 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8030 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), //NON-NLS
8031 				rs.getShort("meta_flags"), rs.getLong("size"), //NON-NLS
8032 				rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
8033 				rs.getShort("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS
8034 				rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS
8035 				rs.getString("parent_path")); //NON-NLS
8036 		dir.setFileSystem(fs);
8037 		return dir;
8038 	}
8039 
8040 	/**
8041 	 * Create a virtual directory object from a result set.
8042 	 *
8043 	 * @param rs         the result set.
8044 	 * @param connection The case database connection.
8045 	 *
8046 	 * @return newly created VirtualDirectory object.
8047 	 *
8048 	 * @throws SQLException
8049 	 */
8050 	VirtualDirectory virtualDirectory(ResultSet rs, CaseDbConnection connection) throws SQLException {
8051 		String parentPath = rs.getString("parent_path"); //NON-NLS
8052 		if (parentPath == null) {
8053 			parentPath = "";
8054 		}
8055 
8056 		long objId = rs.getLong("obj_id");
8057 		long dsObjId = rs.getLong("data_source_obj_id");
8058 		if (objId == dsObjId) {	// virtual directory is a data source
8059 
8060 			String deviceId = "";
8061 			String timeZone = "";
8062 			Statement s = null;
8063 			ResultSet rsDataSourceInfo = null;
8064 
8065 			acquireSingleUserCaseReadLock();
8066 			try {
8067 				s = connection.createStatement();
8068 				rsDataSourceInfo = connection.executeQuery(s, "SELECT device_id, time_zone FROM data_source_info WHERE obj_id = " + objId);
8069 				if (rsDataSourceInfo.next()) {
8070 					deviceId = rsDataSourceInfo.getString("device_id");
8071 					timeZone = rsDataSourceInfo.getString("time_zone");
8072 				}
8073 			} catch (SQLException ex) {
8074 				logger.log(Level.SEVERE, "Error data source info for datasource id " + objId, ex); //NON-NLS
8075 			} finally {
8076 				closeResultSet(rsDataSourceInfo);
8077 				closeStatement(s);
8078 				releaseSingleUserCaseReadLock();
8079 			}
8080 
8081 			return new LocalFilesDataSource(this,
8082 					objId, dsObjId,
8083 					deviceId,
8084 					rs.getString("name"),
8085 					TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8086 					TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8087 					TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")),
8088 					rs.getShort("meta_flags"),
8089 					timeZone,
8090 					rs.getString("md5"),
8091 					FileKnown.valueOf(rs.getByte("known")),
8092 					parentPath);
8093 		} else {
8094 			final VirtualDirectory vd = new VirtualDirectory(this,
8095 					objId, dsObjId,
8096 					rs.getString("name"), //NON-NLS
8097 					TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8098 					TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8099 					TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), //NON-NLS
8100 					rs.getShort("meta_flags"), rs.getString("md5"), //NON-NLS
8101 					FileKnown.valueOf(rs.getByte("known")), parentPath); //NON-NLS
8102 			return vd;
8103 		}
8104 	}
8105 
8106 	/**
8107 	 * Create a virtual directory object from a result set
8108 	 *
8109 	 * @param rs the result set
8110 	 *
8111 	 * @return newly created VirtualDirectory object
8112 	 *
8113 	 * @throws SQLException
8114 	 */
8115 	LocalDirectory localDirectory(ResultSet rs) throws SQLException {
8116 		String parentPath = rs.getString("parent_path"); //NON-NLS
8117 		if (parentPath == null) {
8118 			parentPath = "";
8119 		}
8120 		final LocalDirectory ld = new LocalDirectory(this, rs.getLong("obj_id"), //NON-NLS
8121 				rs.getLong("data_source_obj_id"), rs.getString("name"), //NON-NLS
8122 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8123 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8124 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), //NON-NLS
8125 				rs.getShort("meta_flags"), rs.getString("md5"), //NON-NLS
8126 				FileKnown.valueOf(rs.getByte("known")), parentPath); //NON-NLS
8127 		return ld;
8128 	}
8129 
8130 	/**
8131 	 * Creates a DerivedFile object using the values of a given result set.
8132 	 *
8133 	 * @param rs         The result set.
8134 	 * @param connection The case database connection.
8135 	 * @param parentId   The parent id for the derived file or
8136 	 *                   AbstractContent.UNKNOWN_ID.
8137 	 *
8138 	 * @return The DerivedFile object.
8139 	 *
8140 	 * @throws SQLException if there is an error reading from the result set or
8141 	 *                      doing additional queries.
8142 	 */
8143 	private DerivedFile derivedFile(ResultSet rs, CaseDbConnection connection, long parentId) throws SQLException {
8144 		boolean hasLocalPath = rs.getBoolean("has_path"); //NON-NLS
8145 		long objId = rs.getLong("obj_id"); //NON-NLS
8146 		String localPath = null;
8147 		TskData.EncodingType encodingType = TskData.EncodingType.NONE;
8148 		if (hasLocalPath) {
8149 			ResultSet rsFilePath = null;
8150 			acquireSingleUserCaseReadLock();
8151 			try {
8152 				PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_LOCAL_PATH_AND_ENCODING_FOR_FILE);
8153 				statement.clearParameters();
8154 				statement.setLong(1, objId);
8155 				rsFilePath = connection.executeQuery(statement);
8156 				if (rsFilePath.next()) {
8157 					localPath = rsFilePath.getString("path");
8158 					encodingType = TskData.EncodingType.valueOf(rsFilePath.getInt("encoding_type"));
8159 				}
8160 			} catch (SQLException ex) {
8161 				logger.log(Level.SEVERE, "Error getting encoding type for file " + objId, ex); //NON-NLS
8162 			} finally {
8163 				closeResultSet(rsFilePath);
8164 				releaseSingleUserCaseReadLock();
8165 			}
8166 		}
8167 		String parentPath = rs.getString("parent_path"); //NON-NLS
8168 		if (parentPath == null) {
8169 			parentPath = "";
8170 		}
8171 		final DerivedFile df = new DerivedFile(this, objId, rs.getLong("data_source_obj_id"),
8172 				rs.getString("name"), //NON-NLS
8173 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8174 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8175 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), rs.getShort("meta_flags"), //NON-NLS
8176 				rs.getLong("size"), //NON-NLS
8177 				rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
8178 				rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS
8179 				parentPath, localPath, parentId, rs.getString("mime_type"),
8180 				encodingType, rs.getString("extension"));
8181 		return df;
8182 	}
8183 
8184 	/**
8185 	 * Creates a LocalFile object using the data from a given result set.
8186 	 *
8187 	 * @param rs         The result set.
8188 	 * @param connection The case database connection.
8189 	 * @param parentId   The parent id for the derived file or
8190 	 *                   AbstractContent.UNKNOWN_ID.
8191 	 *
8192 	 * @return The LocalFile object.
8193 	 *
8194 	 * @throws SQLException if there is an error reading from the result set or
8195 	 *                      doing additional queries.
8196 	 */
8197 	private LocalFile localFile(ResultSet rs, CaseDbConnection connection, long parentId) throws SQLException {
8198 		long objId = rs.getLong("obj_id"); //NON-NLS
8199 		String localPath = null;
8200 		TskData.EncodingType encodingType = TskData.EncodingType.NONE;
8201 		if (rs.getBoolean("has_path")) {
8202 			ResultSet rsFilePath = null;
8203 			acquireSingleUserCaseReadLock();
8204 			try {
8205 				PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_LOCAL_PATH_AND_ENCODING_FOR_FILE);
8206 				statement.clearParameters();
8207 				statement.setLong(1, objId);
8208 				rsFilePath = connection.executeQuery(statement);
8209 				if (rsFilePath.next()) {
8210 					localPath = rsFilePath.getString("path");
8211 					encodingType = TskData.EncodingType.valueOf(rsFilePath.getInt("encoding_type"));
8212 				}
8213 			} catch (SQLException ex) {
8214 				logger.log(Level.SEVERE, "Error getting encoding type for file " + objId, ex); //NON-NLS
8215 			} finally {
8216 				closeResultSet(rsFilePath);
8217 				releaseSingleUserCaseReadLock();
8218 			}
8219 		}
8220 		String parentPath = rs.getString("parent_path"); //NON-NLS
8221 		if (null == parentPath) {
8222 			parentPath = "";
8223 		}
8224 		LocalFile file = new LocalFile(this, objId, rs.getString("name"), //NON-NLS
8225 				TSK_DB_FILES_TYPE_ENUM.valueOf(rs.getShort("type")), //NON-NLS
8226 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8227 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8228 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), rs.getShort("meta_flags"), //NON-NLS
8229 				rs.getLong("size"), //NON-NLS
8230 				rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
8231 				rs.getString("mime_type"), rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS
8232 				parentId, parentPath, rs.getLong("data_source_obj_id"),
8233 				localPath, encodingType, rs.getString("extension"));
8234 		return file;
8235 	}
8236 
8237 	/**
8238 	 * Create a Slack File object from the result set containing query results
8239 	 * on tsk_files table
8240 	 *
8241 	 * @param rs the result set
8242 	 * @param fs parent file system
8243 	 *
8244 	 * @return a newly created Slack File
8245 	 *
8246 	 * @throws SQLException
8247 	 */
8248 	org.sleuthkit.datamodel.SlackFile slackFile(ResultSet rs, FileSystem fs) throws SQLException {
8249 		org.sleuthkit.datamodel.SlackFile f = new org.sleuthkit.datamodel.SlackFile(this, rs.getLong("obj_id"), //NON-NLS
8250 				rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS
8251 				TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS
8252 				rs.getInt("attr_id"), rs.getString("name"), rs.getLong("meta_addr"), rs.getInt("meta_seq"), //NON-NLS
8253 				TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS
8254 				TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")), //NON-NLS
8255 				TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), //NON-NLS
8256 				rs.getShort("meta_flags"), rs.getLong("size"), //NON-NLS
8257 				rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS
8258 				(short) rs.getInt("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS
8259 				rs.getString("md5"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS
8260 				rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension")); //NON-NLS
8261 		f.setFileSystem(fs);
8262 		return f;
8263 	}
8264 
8265 	/**
8266 	 * Returns the list of abstractFile objects from a result of selecting many
8267 	 * files that meet a certain criteria.
8268 	 *
8269 	 * @param rs
8270 	 * @param parentId
8271 	 *
8272 	 * @return
8273 	 *
8274 	 * @throws SQLException
8275 	 */
8276 	List<Content> fileChildren(ResultSet rs, CaseDbConnection connection, long parentId) throws SQLException {
8277 		List<Content> children = new ArrayList<Content>();
8278 
8279 		while (rs.next()) {
8280 			TskData.TSK_DB_FILES_TYPE_ENUM type = TskData.TSK_DB_FILES_TYPE_ENUM.valueOf(rs.getShort("type"));
8281 
8282 			if (null != type) {
8283 				switch (type) {
8284 					case FS:
8285 						if (rs.getShort("meta_type") != TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR.getValue()) {
8286 							FsContent result;
8287 							if (rs.getShort("meta_type") == TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()) {
8288 								result = directory(rs, null);
8289 							} else {
8290 								result = file(rs, null);
8291 							}
8292 							children.add(result);
8293 						} else {
8294 							VirtualDirectory virtDir = virtualDirectory(rs, connection);
8295 							children.add(virtDir);
8296 						}
8297 						break;
8298 					case VIRTUAL_DIR:
8299 						VirtualDirectory virtDir = virtualDirectory(rs, connection);
8300 						children.add(virtDir);
8301 						break;
8302 					case LOCAL_DIR:
8303 						LocalDirectory localDir = localDirectory(rs);
8304 						children.add(localDir);
8305 						break;
8306 					case UNALLOC_BLOCKS:
8307 					case UNUSED_BLOCKS:
8308 					case CARVED:
8309 					case LAYOUT_FILE: {
8310 						String parentPath = rs.getString("parent_path");
8311 						if (parentPath == null) {
8312 							parentPath = "";
8313 						}
8314 						final LayoutFile lf = new LayoutFile(this, rs.getLong("obj_id"),
8315 								rs.getLong("data_source_obj_id"), rs.getString("name"), type,
8316 								TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")),
8317 								TSK_FS_META_TYPE_ENUM.valueOf(rs.getShort("meta_type")),
8318 								TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), rs.getShort("meta_flags"),
8319 								rs.getLong("size"),
8320 								rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"),
8321 								rs.getString("md5"),
8322 								FileKnown.valueOf(rs.getByte("known")), parentPath, rs.getString("mime_type"));
8323 						children.add(lf);
8324 						break;
8325 					}
8326 					case DERIVED:
8327 						final DerivedFile df = derivedFile(rs, connection, parentId);
8328 						children.add(df);
8329 						break;
8330 					case LOCAL: {
8331 						final LocalFile lf = localFile(rs, connection, parentId);
8332 						children.add(lf);
8333 						break;
8334 					}
8335 					case SLACK: {
8336 						final SlackFile sf = slackFile(rs, null);
8337 						children.add(sf);
8338 						break;
8339 					}
8340 					default:
8341 						break;
8342 				}
8343 			}
8344 		}
8345 		return children;
8346 	}
8347 
8348 	/**
8349 	 * Creates BlackboardArtifact objects for the result set of a
8350 	 * blackboard_artifacts table query of the form "SELECT * FROM
8351 	 * blackboard_artifacts WHERE XYZ".
8352 	 *
8353 	 * @param rs A result set from a query of the blackboard_artifacts table of
8354 	 *           the form "SELECT * FROM blackboard_artifacts WHERE XYZ".
8355 	 *
8356 	 * @return A list of BlackboardArtifact objects.
8357 	 *
8358 	 * @throws SQLException     Thrown if there is a problem iterating through
8359 	 *                          the result set.
8360 	 * @throws TskCoreException Thrown if there is an error looking up the
8361 	 *                          artifact type id
8362 	 */
8363 	private List<BlackboardArtifact> resultSetToArtifacts(ResultSet rs) throws SQLException, TskCoreException {
8364 		ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
8365 		try {
8366 			while (rs.next()) {
8367 				BlackboardArtifact.Type artifactType = getArtifactType(rs.getInt("artifact_type_id"));
8368 				if (artifactType != null) {
8369 					artifacts.add(new BlackboardArtifact(this, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
8370 							rs.getInt("artifact_type_id"), artifactType.getTypeName(), artifactType.getDisplayName(),
8371 							BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
8372 				} else {
8373 					throw new TskCoreException("Error looking up artifact type ID " + rs.getInt("artifact_type_id") + " from artifact " + rs.getLong("artifact_id"));
8374 				}
8375 			} //end for each resultSet
8376 		} catch (SQLException e) {
8377 			logger.log(Level.SEVERE, "Error getting artifacts from result set", e); //NON-NLS
8378 		}
8379 
8380 		return artifacts;
8381 	}
8382 
8383 	/**
8384 	 * This method allows developers to run arbitrary SQL "SELECT" queries. The
8385 	 * CaseDbQuery object will take care of acquiring the necessary database
8386 	 * lock and when used in a try-with-resources block will automatically take
8387 	 * care of releasing the lock. If you do not use a try-with-resources block
8388 	 * you must call CaseDbQuery.close() once you are done processing the files
8389 	 * of the query.
8390 	 *
8391 	 * Also note that if you use it within a transaction to insert something
8392 	 * into the database, and then within that same transaction query the
8393 	 * inserted item from the database, you will likely not see your inserted
8394 	 * item, as the method uses new connections for each execution. With this
8395 	 * method, you must close your transaction before successfully querying for
8396 	 * newly-inserted items.
8397 	 *
8398 	 * @param query The query string to execute.
8399 	 *
8400 	 * @return A CaseDbQuery instance.
8401 	 *
8402 	 * @throws TskCoreException
8403 	 */
8404 	public CaseDbQuery executeQuery(String query) throws TskCoreException {
8405 		return new CaseDbQuery(query);
8406 	}
8407 
8408 	/**
8409 	 * This method allows developers to run arbitrary SQL queries, including
8410 	 * INSERT and UPDATE. The CaseDbQuery object will take care of acquiring the
8411 	 * necessary database lock and when used in a try-with-resources block will
8412 	 * automatically take care of releasing the lock. If you do not use a
8413 	 * try-with-resources block you must call CaseDbQuery.close() once you are
8414 	 * done processing the files of the query.
8415 	 *
8416 	 * Also note that if you use it within a transaction to insert something
8417 	 * into the database, and then within that same transaction query the
8418 	 * inserted item from the database, you will likely not see your inserted
8419 	 * item, as the method uses new connections for each execution. With this
8420 	 * method, you must close your transaction before successfully querying for
8421 	 * newly-inserted items.
8422 	 *
8423 	 * @param query The query string to execute.
8424 	 *
8425 	 * @return A CaseDbQuery instance.
8426 	 *
8427 	 * @throws TskCoreException
8428 	 */
8429 	public CaseDbQuery executeInsertOrUpdate(String query) throws TskCoreException {
8430 		return new CaseDbQuery(query, true);
8431 	}
8432 
8433 	/**
8434 	 * Get a case database connection.
8435 	 *
8436 	 * @return The case database connection.
8437 	 *
8438 	 * @throws TskCoreException
8439 	 */
8440 	CaseDbConnection getConnection() throws TskCoreException {
8441 		return connections.getConnection();
8442 	}
8443 
8444 	SleuthkitJNI.CaseDbHandle getCaseHandle() {
8445 		return this.caseHandle;
8446 	}
8447 
8448 	@Override
8449 	protected void finalize() throws Throwable {
8450 		try {
8451 			close();
8452 		} finally {
8453 			super.finalize();
8454 		}
8455 	}
8456 
8457 	/**
8458 	 * Call to free resources when done with instance.
8459 	 */
8460 	public synchronized void close() {
8461 		acquireSingleUserCaseWriteLock();
8462 
8463 		try {
8464 			connections.close();
8465 		} catch (TskCoreException ex) {
8466 			logger.log(Level.SEVERE, "Error closing database connection pool.", ex); //NON-NLS
8467 		}
8468 
8469 		fileSystemIdMap.clear();
8470 
8471 		try {
8472 			if (this.caseHandle != null) {
8473 				this.caseHandle.free();
8474 				this.caseHandle = null;
8475 			}
8476 		} catch (TskCoreException ex) {
8477 			logger.log(Level.SEVERE, "Error freeing case handle.", ex); //NON-NLS
8478 		} finally {
8479 			releaseSingleUserCaseWriteLock();
8480 		}
8481 	}
8482 
8483 	/**
8484 	 * Store the known status for the FsContent in the database Note: will not
8485 	 * update status if content is already 'Known Bad'
8486 	 *
8487 	 * @param	file      The AbstractFile object
8488 	 * @param	fileKnown The object's known status
8489 	 *
8490 	 * @return	true if the known status was updated, false otherwise
8491 	 *
8492 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8493 	 *                          core
8494 	 */
8495 	public boolean setKnown(AbstractFile file, FileKnown fileKnown) throws TskCoreException {
8496 		long id = file.getId();
8497 		FileKnown currentKnown = file.getKnown();
8498 		if (currentKnown.compareTo(fileKnown) > 0) {
8499 			return false;
8500 		}
8501 		CaseDbConnection connection = connections.getConnection();
8502 		acquireSingleUserCaseWriteLock();
8503 		Statement statement = null;
8504 		try {
8505 			statement = connection.createStatement();
8506 			connection.executeUpdate(statement, "UPDATE tsk_files " //NON-NLS
8507 					+ "SET known='" + fileKnown.getFileKnownValue() + "' " //NON-NLS
8508 					+ "WHERE obj_id=" + id); //NON-NLS
8509 
8510 			file.setKnown(fileKnown);
8511 		} catch (SQLException ex) {
8512 			throw new TskCoreException("Error setting Known status.", ex);
8513 		} finally {
8514 			closeStatement(statement);
8515 			connection.close();
8516 			releaseSingleUserCaseWriteLock();
8517 		}
8518 		return true;
8519 	}
8520 
8521 	/**
8522 	 * Set the name of an object in the tsk_files table.
8523 	 *
8524 	 * @param name  The new name for the object
8525 	 * @param objId The object ID
8526 	 *
8527 	 * @throws TskCoreException If there is an error updating the case database.
8528 	 */
8529 	void setFileName(String name, long objId) throws TskCoreException {
8530 
8531 		CaseDbConnection connection = connections.getConnection();
8532 		acquireSingleUserCaseWriteLock();
8533 		try {
8534 			PreparedStatement preparedStatement = connection.getPreparedStatement(SleuthkitCase.PREPARED_STATEMENT.UPDATE_FILE_NAME);
8535 			preparedStatement.clearParameters();
8536 			preparedStatement.setString(1, name);
8537 			preparedStatement.setLong(2, objId);
8538 			connection.executeUpdate(preparedStatement);
8539 		} catch (SQLException ex) {
8540 			throw new TskCoreException(String.format("Error updating while the name for object ID %d to %s", objId, name), ex);
8541 		} finally {
8542 			connection.close();
8543 			releaseSingleUserCaseWriteLock();
8544 		}
8545 	}
8546 
8547 	/**
8548 	 * Set the display name of an image in the tsk_image_info table.
8549 	 *
8550 	 * @param name  The new name for the image
8551 	 * @param objId The object ID
8552 	 *
8553 	 * @throws TskCoreException If there is an error updating the case database.
8554 	 */
8555 	void setImageName(String name, long objId) throws TskCoreException {
8556 
8557 		CaseDbConnection connection = connections.getConnection();
8558 		acquireSingleUserCaseWriteLock();
8559 		try {
8560 			PreparedStatement preparedStatement = connection.getPreparedStatement(SleuthkitCase.PREPARED_STATEMENT.UPDATE_IMAGE_NAME);
8561 			preparedStatement.clearParameters();
8562 			preparedStatement.setString(1, name);
8563 			preparedStatement.setLong(2, objId);
8564 			connection.executeUpdate(preparedStatement);
8565 		} catch (SQLException ex) {
8566 			throw new TskCoreException(String.format("Error updating while the name for object ID %d to %s", objId, name), ex);
8567 		} finally {
8568 			connection.close();
8569 			releaseSingleUserCaseWriteLock();
8570 		}
8571 	}
8572 
8573 	/**
8574 	 * Stores the MIME type of a file in the case database and updates the MIME
8575 	 * type of the given file object.
8576 	 *
8577 	 * @param file     A file.
8578 	 * @param mimeType The MIME type.
8579 	 *
8580 	 * @throws TskCoreException If there is an error updating the case database.
8581 	 */
8582 	public void setFileMIMEType(AbstractFile file, String mimeType) throws TskCoreException {
8583 		CaseDbConnection connection = connections.getConnection();
8584 		Statement statement = null;
8585 		ResultSet rs = null;
8586 		acquireSingleUserCaseWriteLock();
8587 		try {
8588 			statement = connection.createStatement();
8589 			connection.executeUpdate(statement, String.format("UPDATE tsk_files SET mime_type = '%s' WHERE obj_id = %d", mimeType, file.getId()));
8590 			file.setMIMEType(mimeType);
8591 		} catch (SQLException ex) {
8592 			throw new TskCoreException(String.format("Error setting MIME type for file (obj_id = %s)", file.getId()), ex);
8593 		} finally {
8594 			closeResultSet(rs);
8595 			closeStatement(statement);
8596 			connection.close();
8597 			releaseSingleUserCaseWriteLock();
8598 		}
8599 	}
8600 
8601 	/**
8602 	 * Store the md5Hash for the file in the database
8603 	 *
8604 	 * @param	file    The file object
8605 	 * @param	md5Hash The object's md5Hash
8606 	 *
8607 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8608 	 *                          core
8609 	 */
8610 	void setMd5Hash(AbstractFile file, String md5Hash) throws TskCoreException {
8611 		if (md5Hash == null) {
8612 			return;
8613 		}
8614 		long id = file.getId();
8615 		CaseDbConnection connection = connections.getConnection();
8616 		acquireSingleUserCaseWriteLock();
8617 		try {
8618 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_FILE_MD5);
8619 			statement.clearParameters();
8620 			statement.setString(1, md5Hash.toLowerCase());
8621 			statement.setLong(2, id);
8622 			connection.executeUpdate(statement);
8623 			file.setMd5Hash(md5Hash.toLowerCase());
8624 		} catch (SQLException ex) {
8625 			throw new TskCoreException("Error setting MD5 hash", ex);
8626 		} finally {
8627 			connection.close();
8628 			releaseSingleUserCaseWriteLock();
8629 		}
8630 	}
8631 
8632 	/**
8633 	 * Store the MD5 hash for the image in the database
8634 	 *
8635 	 * @param	img     The image object
8636 	 * @param	md5Hash The image's MD5 hash
8637 	 *
8638 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8639 	 *                          core
8640 	 */
8641 	void setMd5ImageHash(Image img, String md5Hash) throws TskCoreException {
8642 		if (md5Hash == null) {
8643 			return;
8644 		}
8645 		long id = img.getId();
8646 		CaseDbConnection connection = connections.getConnection();
8647 		acquireSingleUserCaseWriteLock();
8648 		try {
8649 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_IMAGE_MD5);
8650 			statement.clearParameters();
8651 			statement.setString(1, md5Hash.toLowerCase());
8652 			statement.setLong(2, id);
8653 			connection.executeUpdate(statement);
8654 		} catch (SQLException ex) {
8655 			throw new TskCoreException("Error setting MD5 hash", ex);
8656 		} finally {
8657 			connection.close();
8658 			releaseSingleUserCaseWriteLock();
8659 		}
8660 	}
8661 
8662 	/**
8663 	 * Get the MD5 hash of an image from the case database
8664 	 *
8665 	 * @param The image object
8666 	 *
8667 	 * @return The image's MD5 hash
8668 	 *
8669 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8670 	 *                          core
8671 	 */
8672 	String getMd5ImageHash(Image img) throws TskCoreException {
8673 		long id = img.getId();
8674 		CaseDbConnection connection = connections.getConnection();
8675 		acquireSingleUserCaseReadLock();
8676 		ResultSet rs = null;
8677 		String hash = "";
8678 		try {
8679 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_IMAGE_MD5);
8680 			statement.clearParameters();
8681 			statement.setLong(1, id);
8682 			rs = connection.executeQuery(statement);
8683 			if (rs.next()) {
8684 				hash = rs.getString("md5");
8685 			}
8686 			return hash;
8687 		} catch (SQLException ex) {
8688 			throw new TskCoreException("Error getting MD5 hash", ex);
8689 		} finally {
8690 			closeResultSet(rs);
8691 			connection.close();
8692 			releaseSingleUserCaseReadLock();
8693 		}
8694 	}
8695 
8696 	/**
8697 	 * Store the SHA1 hash for the image in the database
8698 	 *
8699 	 * @param	img      The image object
8700 	 * @param	sha1Hash The image's sha1 hash
8701 	 *
8702 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8703 	 *                          core
8704 	 */
8705 	void setSha1ImageHash(Image img, String sha1Hash) throws TskCoreException {
8706 		if (sha1Hash == null) {
8707 			return;
8708 		}
8709 		long id = img.getId();
8710 		CaseDbConnection connection = connections.getConnection();
8711 		acquireSingleUserCaseWriteLock();
8712 		try {
8713 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_IMAGE_SHA1);
8714 			statement.clearParameters();
8715 			statement.setString(1, sha1Hash.toLowerCase());
8716 			statement.setLong(2, id);
8717 			connection.executeUpdate(statement);
8718 		} catch (SQLException ex) {
8719 			throw new TskCoreException("Error setting SHA1 hash", ex);
8720 		} finally {
8721 			connection.close();
8722 			releaseSingleUserCaseWriteLock();
8723 		}
8724 	}
8725 
8726 	/**
8727 	 * Get the SHA1 hash of an image from the case database
8728 	 *
8729 	 * @param The image object
8730 	 *
8731 	 * @return The image's SHA1 hash
8732 	 *
8733 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8734 	 *                          core
8735 	 */
8736 	String getSha1ImageHash(Image img) throws TskCoreException {
8737 		long id = img.getId();
8738 		CaseDbConnection connection = connections.getConnection();
8739 		acquireSingleUserCaseReadLock();
8740 		ResultSet rs = null;
8741 		String hash = "";
8742 		try {
8743 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_IMAGE_SHA1);
8744 			statement.clearParameters();
8745 			statement.setLong(1, id);
8746 			rs = connection.executeQuery(statement);
8747 			if (rs.next()) {
8748 				hash = rs.getString("sha1");
8749 			}
8750 			return hash;
8751 		} catch (SQLException ex) {
8752 			throw new TskCoreException("Error getting SHA1 hash", ex);
8753 		} finally {
8754 			closeResultSet(rs);
8755 			connection.close();
8756 			releaseSingleUserCaseReadLock();
8757 		}
8758 	}
8759 
8760 	/**
8761 	 * Store the SHA256 hash for the file in the database
8762 	 *
8763 	 * @param	img        The image object
8764 	 * @param	sha256Hash The object's md5Hash
8765 	 *
8766 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8767 	 *                          core
8768 	 */
8769 	void setSha256ImageHash(Image img, String sha256Hash) throws TskCoreException {
8770 		if (sha256Hash == null) {
8771 			return;
8772 		}
8773 		long id = img.getId();
8774 		CaseDbConnection connection = connections.getConnection();
8775 		acquireSingleUserCaseWriteLock();
8776 		try {
8777 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_IMAGE_SHA256);
8778 			statement.clearParameters();
8779 			statement.setString(1, sha256Hash.toLowerCase());
8780 			statement.setLong(2, id);
8781 			connection.executeUpdate(statement);
8782 		} catch (SQLException ex) {
8783 			throw new TskCoreException("Error setting SHA256 hash", ex);
8784 		} finally {
8785 			connection.close();
8786 			releaseSingleUserCaseWriteLock();
8787 		}
8788 	}
8789 
8790 	/**
8791 	 * Get the SHA256 hash of an image from the case database
8792 	 *
8793 	 * @param The image object
8794 	 *
8795 	 * @return The image's SHA256 hash
8796 	 *
8797 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8798 	 *                          core
8799 	 */
8800 	String getSha256ImageHash(Image img) throws TskCoreException {
8801 		long id = img.getId();
8802 		CaseDbConnection connection = connections.getConnection();
8803 		acquireSingleUserCaseReadLock();
8804 		ResultSet rs = null;
8805 		String hash = "";
8806 		try {
8807 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_IMAGE_SHA256);
8808 			statement.clearParameters();
8809 			statement.setLong(1, id);
8810 			rs = connection.executeQuery(statement);
8811 			if (rs.next()) {
8812 				hash = rs.getString("sha256");
8813 			}
8814 			return hash;
8815 		} catch (SQLException ex) {
8816 			throw new TskCoreException("Error setting SHA256 hash", ex);
8817 		} finally {
8818 			closeResultSet(rs);
8819 			connection.close();
8820 			releaseSingleUserCaseReadLock();
8821 		}
8822 	}
8823 
8824 	/**
8825 	 * Set the acquisition details in the data_source_info table
8826 	 *
8827 	 * @param datasource The data source
8828 	 * @param details    The acquisition details
8829 	 *
8830 	 * @throws TskCoreException Thrown if the database write fails
8831 	 */
8832 	void setAcquisitionDetails(DataSource datasource, String details) throws TskCoreException {
8833 
8834 		long id = datasource.getId();
8835 		CaseDbConnection connection = connections.getConnection();
8836 		acquireSingleUserCaseWriteLock();
8837 		try {
8838 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_ACQUISITION_DETAILS);
8839 			statement.clearParameters();
8840 			statement.setString(1, details);
8841 			statement.setLong(2, id);
8842 			connection.executeUpdate(statement);
8843 		} catch (SQLException ex) {
8844 			throw new TskCoreException("Error setting acquisition details", ex);
8845 		} finally {
8846 			connection.close();
8847 			releaseSingleUserCaseWriteLock();
8848 		}
8849 	}
8850 
8851 	/**
8852 	 * Get the acquisition details from the data_source_info table
8853 	 *
8854 	 * @param datasource The data source
8855 	 *
8856 	 * @return The acquisition details
8857 	 *
8858 	 * @throws TskCoreException Thrown if the database read fails
8859 	 */
8860 	String getAcquisitionDetails(DataSource datasource) throws TskCoreException {
8861 		long id = datasource.getId();
8862 		CaseDbConnection connection = connections.getConnection();
8863 		acquireSingleUserCaseReadLock();
8864 		ResultSet rs = null;
8865 		String hash = "";
8866 		try {
8867 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ACQUISITION_DETAILS);
8868 			statement.clearParameters();
8869 			statement.setLong(1, id);
8870 			rs = connection.executeQuery(statement);
8871 			if (rs.next()) {
8872 				hash = rs.getString("acquisition_details");
8873 			}
8874 			return hash;
8875 		} catch (SQLException ex) {
8876 			throw new TskCoreException("Error setting acquisition details", ex);
8877 		} finally {
8878 			closeResultSet(rs);
8879 			connection.close();
8880 			releaseSingleUserCaseReadLock();
8881 		}
8882 	}
8883 
8884 	/**
8885 	 * Set the review status of the given artifact to newStatus
8886 	 *
8887 	 * @param artifact  The artifact whose review status is being set.
8888 	 * @param newStatus The new review status for the given artifact. Must not
8889 	 *                  be null.
8890 	 *
8891 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8892 	 *                          core
8893 	 */
8894 	public void setReviewStatus(BlackboardArtifact artifact, BlackboardArtifact.ReviewStatus newStatus) throws TskCoreException {
8895 		if (newStatus == null) {
8896 			return;
8897 		}
8898 		CaseDbConnection connection = connections.getConnection();
8899 		acquireSingleUserCaseWriteLock();
8900 		Statement statement = null;
8901 		try {
8902 			statement = connection.createStatement();
8903 			connection.executeUpdate(statement, "UPDATE blackboard_artifacts "
8904 					+ " SET review_status_id=" + newStatus.getID()
8905 					+ " WHERE blackboard_artifacts.artifact_id = " + artifact.getArtifactID());
8906 		} catch (SQLException ex) {
8907 			throw new TskCoreException("Error setting review status", ex);
8908 		} finally {
8909 			closeStatement(statement);
8910 			connection.close();
8911 			releaseSingleUserCaseWriteLock();
8912 		}
8913 	}
8914 
8915 	/**
8916 	 * Return the number of objects in the database of a given file type.
8917 	 *
8918 	 * @param contentType Type of file to count
8919 	 *
8920 	 * @return Number of objects with that type.
8921 	 *
8922 	 * @throws TskCoreException thrown if a critical error occurred within tsk
8923 	 *                          core
8924 	 */
8925 	public int countFsContentType(TskData.TSK_FS_META_TYPE_ENUM contentType) throws TskCoreException {
8926 		CaseDbConnection connection = connections.getConnection();
8927 		acquireSingleUserCaseReadLock();
8928 		Statement s = null;
8929 		ResultSet rs = null;
8930 		try {
8931 			s = connection.createStatement();
8932 			Short contentShort = contentType.getValue();
8933 			rs = connection.executeQuery(s, "SELECT COUNT(*) AS count FROM tsk_files WHERE meta_type = '" + contentShort.toString() + "'"); //NON-NLS
8934 			int count = 0;
8935 			if (rs.next()) {
8936 				count = rs.getInt("count");
8937 			}
8938 			return count;
8939 		} catch (SQLException ex) {
8940 			throw new TskCoreException("Error getting number of objects.", ex);
8941 		} finally {
8942 			closeResultSet(rs);
8943 			closeStatement(s);
8944 			connection.close();
8945 			releaseSingleUserCaseReadLock();
8946 		}
8947 	}
8948 
8949 	/**
8950 	 * Escape the single quotes in the given string so they can be added to the
8951 	 * SQL caseDbConnection
8952 	 *
8953 	 * @param text
8954 	 *
8955 	 * @return text the escaped version
8956 	 */
8957 	public static String escapeSingleQuotes(String text) {
8958 		String escapedText = null;
8959 		if (text != null) {
8960 			escapedText = text.replaceAll("'", "''");
8961 		}
8962 		return escapedText;
8963 	}
8964 
8965 	/**
8966 	 * Find all the files with the given MD5 hash.
8967 	 *
8968 	 * @param md5Hash hash value to match files with
8969 	 *
8970 	 * @return List of AbstractFile with the given hash
8971 	 */
8972 	public List<AbstractFile> findFilesByMd5(String md5Hash) {
8973 		if (md5Hash == null) {
8974 			return Collections.<AbstractFile>emptyList();
8975 		}
8976 		CaseDbConnection connection;
8977 		try {
8978 			connection = connections.getConnection();
8979 		} catch (TskCoreException ex) {
8980 			logger.log(Level.SEVERE, "Error finding files by md5 hash " + md5Hash, ex); //NON-NLS
8981 			return Collections.<AbstractFile>emptyList();
8982 		}
8983 		acquireSingleUserCaseReadLock();
8984 		Statement s = null;
8985 		ResultSet rs = null;
8986 		try {
8987 			s = connection.createStatement();
8988 			rs = connection.executeQuery(s, "SELECT * FROM tsk_files WHERE " //NON-NLS
8989 					+ " md5 = '" + md5Hash.toLowerCase() + "' " //NON-NLS
8990 					+ "AND size > 0"); //NON-NLS
8991 			return resultSetToAbstractFiles(rs, connection);
8992 		} catch (SQLException ex) {
8993 			logger.log(Level.WARNING, "Error querying database.", ex); //NON-NLS
8994 			return Collections.<AbstractFile>emptyList();
8995 		} finally {
8996 			closeResultSet(rs);
8997 			closeStatement(s);
8998 			connection.close();
8999 			releaseSingleUserCaseReadLock();
9000 		}
9001 	}
9002 
9003 	/**
9004 	 * Query all the files to verify if they have an MD5 hash associated with
9005 	 * them.
9006 	 *
9007 	 * @return true if all files have an MD5 hash
9008 	 */
9009 	public boolean allFilesMd5Hashed() {
9010 		CaseDbConnection connection;
9011 		try {
9012 			connection = connections.getConnection();
9013 		} catch (TskCoreException ex) {
9014 			logger.log(Level.SEVERE, "Error checking md5 hashing status", ex); //NON-NLS
9015 			return false;
9016 		}
9017 		boolean allFilesAreHashed = false;
9018 		acquireSingleUserCaseReadLock();
9019 		Statement s = null;
9020 		ResultSet rs = null;
9021 		try {
9022 			s = connection.createStatement();
9023 			rs = connection.executeQuery(s, "SELECT COUNT(*) AS count FROM tsk_files " //NON-NLS
9024 					+ "WHERE dir_type = '" + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + "' " //NON-NLS
9025 					+ "AND md5 IS NULL " //NON-NLS
9026 					+ "AND size > '0'"); //NON-NLS
9027 			if (rs.next() && rs.getInt("count") == 0) {
9028 				allFilesAreHashed = true;
9029 			}
9030 		} catch (SQLException ex) {
9031 			logger.log(Level.WARNING, "Failed to query whether all files have MD5 hashes", ex); //NON-NLS
9032 		} finally {
9033 			closeResultSet(rs);
9034 			closeStatement(s);
9035 			connection.close();
9036 			releaseSingleUserCaseReadLock();
9037 		}
9038 		return allFilesAreHashed;
9039 	}
9040 
9041 	/**
9042 	 * Query all the files and counts how many have an MD5 hash.
9043 	 *
9044 	 * @return the number of files with an MD5 hash
9045 	 */
9046 	public int countFilesMd5Hashed() {
9047 		CaseDbConnection connection;
9048 		try {
9049 			connection = connections.getConnection();
9050 		} catch (TskCoreException ex) {
9051 			logger.log(Level.SEVERE, "Error getting database connection for hashed files count", ex); //NON-NLS
9052 			return 0;
9053 		}
9054 		int count = 0;
9055 		acquireSingleUserCaseReadLock();
9056 		Statement s = null;
9057 		ResultSet rs = null;
9058 		try {
9059 			s = connection.createStatement();
9060 			rs = connection.executeQuery(s, "SELECT COUNT(*) AS count FROM tsk_files " //NON-NLS
9061 					+ "WHERE md5 IS NOT NULL " //NON-NLS
9062 					+ "AND size > '0'"); //NON-NLS
9063 			if (rs.next()) {
9064 				count = rs.getInt("count");
9065 			}
9066 		} catch (SQLException ex) {
9067 			logger.log(Level.WARNING, "Failed to query for all the files.", ex); //NON-NLS
9068 		} finally {
9069 			closeResultSet(rs);
9070 			closeStatement(s);
9071 			connection.close();
9072 			releaseSingleUserCaseReadLock();
9073 		}
9074 		return count;
9075 
9076 	}
9077 
9078 	/**
9079 	 * Selects all of the rows from the tag_names table in the case database.
9080 	 *
9081 	 * @return A list, possibly empty, of TagName data transfer objects (DTOs)
9082 	 *         for the rows.
9083 	 *
9084 	 * @throws TskCoreException
9085 	 */
9086 	public List<TagName> getAllTagNames() throws TskCoreException {
9087 		CaseDbConnection connection = connections.getConnection();
9088 		acquireSingleUserCaseReadLock();
9089 		ResultSet resultSet = null;
9090 		try {
9091 			// SELECT * FROM tag_names
9092 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_TAG_NAMES);
9093 			resultSet = connection.executeQuery(statement);
9094 			ArrayList<TagName> tagNames = new ArrayList<TagName>();
9095 			while (resultSet.next()) {
9096 				tagNames.add(new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9097 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9098 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")))); //NON-NLS
9099 			}
9100 			return tagNames;
9101 		} catch (SQLException ex) {
9102 			throw new TskCoreException("Error selecting rows from tag_names table", ex);
9103 		} finally {
9104 			closeResultSet(resultSet);
9105 			connection.close();
9106 			releaseSingleUserCaseReadLock();
9107 		}
9108 	}
9109 
9110 	/**
9111 	 * Selects all of the rows from the tag_names table in the case database for
9112 	 * which there is at least one matching row in the content_tags or
9113 	 * blackboard_artifact_tags tables.
9114 	 *
9115 	 * @return A list, possibly empty, of TagName data transfer objects (DTOs)
9116 	 *         for the rows.
9117 	 *
9118 	 * @throws TskCoreException
9119 	 */
9120 	public List<TagName> getTagNamesInUse() throws TskCoreException {
9121 		CaseDbConnection connection = connections.getConnection();
9122 		acquireSingleUserCaseReadLock();
9123 		ResultSet resultSet = null;
9124 		try {
9125 			// SELECT * FROM tag_names WHERE tag_name_id IN (SELECT tag_name_id from content_tags UNION SELECT tag_name_id FROM blackboard_artifact_tags)
9126 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_TAG_NAMES_IN_USE);
9127 			resultSet = connection.executeQuery(statement);
9128 			ArrayList<TagName> tagNames = new ArrayList<TagName>();
9129 			while (resultSet.next()) {
9130 				tagNames.add(new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9131 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9132 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")))); //NON-NLS
9133 			}
9134 			return tagNames;
9135 		} catch (SQLException ex) {
9136 			throw new TskCoreException("Error selecting rows from tag_names table", ex);
9137 		} finally {
9138 			closeResultSet(resultSet);
9139 			connection.close();
9140 			releaseSingleUserCaseReadLock();
9141 		}
9142 	}
9143 
9144 	/**
9145 	 * Selects all of the rows from the tag_names table in the case database for
9146 	 * which there is at least one matching row in the content_tags or
9147 	 * blackboard_artifact_tags tables, for the given data source object id.
9148 	 *
9149 	 * @param dsObjId data source object id
9150 	 *
9151 	 * @return A list, possibly empty, of TagName data transfer objects (DTOs)
9152 	 *         for the rows.
9153 	 *
9154 	 * @throws TskCoreException
9155 	 */
9156 	public List<TagName> getTagNamesInUse(long dsObjId) throws TskCoreException {
9157 
9158 		ArrayList<TagName> tagNames = new ArrayList<TagName>();
9159 		//	SELECT * FROM tag_names WHERE tag_name_id IN
9160 		//	 ( SELECT content_tags.tag_name_id as tag_name_id FROM content_tags as content_tags, tsk_files as tsk_files WHERE content_tags.obj_id = tsk_files.obj_id AND tsk_files.data_source_obj_id =  ? "
9161 		//     UNION
9162 		//     SELECT artifact_tags.tag_name_id as tag_name_id FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts WHERE artifact_tags.artifact_id = arts.artifact_id AND arts.data_source_obj_id = ? )
9163 		//   )
9164 		CaseDbConnection connection = connections.getConnection();
9165 		acquireSingleUserCaseReadLock();
9166 		ResultSet resultSet = null;
9167 
9168 		try {
9169 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_TAG_NAMES_IN_USE_BY_DATASOURCE);
9170 			statement.setLong(1, dsObjId);
9171 			statement.setLong(2, dsObjId);
9172 			resultSet = connection.executeQuery(statement); //NON-NLS
9173 			while (resultSet.next()) {
9174 				tagNames.add(new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9175 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9176 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")))); //NON-NLS
9177 			}
9178 			return tagNames;
9179 		} catch (SQLException ex) {
9180 			throw new TskCoreException("Failed to get tag names in use for data source objID : " + dsObjId, ex);
9181 		} finally {
9182 			closeResultSet(resultSet);
9183 			connection.close();
9184 			releaseSingleUserCaseReadLock();
9185 		}
9186 	}
9187 
9188 	/**
9189 	 * Inserts row into the tags_names table in the case database.
9190 	 *
9191 	 * @param displayName The display name for the new tag name.
9192 	 * @param description The description for the new tag name.
9193 	 * @param color       The HTML color to associate with the new tag name.
9194 	 *
9195 	 * @return A TagName data transfer object (DTO) for the new row.
9196 	 *
9197 	 * @throws TskCoreException
9198 	 * @deprecated addOrUpdateTagName should be used this method calls
9199 	 * addOrUpdateTagName with a default knownStatus value
9200 	 */
9201 	@Deprecated
9202 	public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TskCoreException {
9203 		return addOrUpdateTagName(displayName, description, color, TskData.FileKnown.UNKNOWN);
9204 	}
9205 
9206 	/**
9207 	 * Inserts row into the tags_names table, or updates the existing row if the
9208 	 * displayName already exists in the tag_names table in the case database.
9209 	 *
9210 	 * @param displayName The display name for the new tag name.
9211 	 * @param description The description for the new tag name.
9212 	 * @param color       The HTML color to associate with the new tag name.
9213 	 * @param knownStatus The TskData.FileKnown value to associate with the new
9214 	 *                    tag name.
9215 	 *
9216 	 * @return A TagName data transfer object (DTO) for the new row.
9217 	 *
9218 	 * @throws TskCoreException
9219 	 */
9220 	public TagName addOrUpdateTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TskCoreException {
9221 		CaseDbConnection connection = connections.getConnection();
9222 		acquireSingleUserCaseWriteLock();
9223 		ResultSet resultSet = null;
9224 		try {
9225 			PreparedStatement statement;
9226 			if (dbType == DbType.POSTGRESQL) {
9227 				// INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?) ON CONFLICT (display_name) DO UPDATE SET description = ?, color = ?, knownStatus = ?
9228 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_OR_UPDATE_TAG_NAME_POSTGRES, Statement.RETURN_GENERATED_KEYS);
9229 				statement.clearParameters();
9230 				statement.setString(5, description);
9231 				statement.setString(6, color.getName());
9232 				statement.setByte(7, knownStatus.getFileKnownValue());
9233 			} else {
9234 				// WITH new (display_name, description, color, knownStatus)
9235 				// AS ( VALUES(?, ?, ?, ?)) INSERT OR REPLACE INTO tag_names
9236 				// (tag_name_id, display_name, description, color, knownStatus)
9237 				// SELECT old.tag_name_id, new.display_name, new.description, new.color, new.knownStatus
9238 				// FROM new LEFT JOIN tag_names AS old ON new.display_name = old.display_name
9239 				statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_OR_UPDATE_TAG_NAME_SQLITE, Statement.RETURN_GENERATED_KEYS);
9240 				statement.clearParameters();
9241 			}
9242 			statement.setString(1, displayName);
9243 			statement.setString(2, description);
9244 			statement.setString(3, color.getName());
9245 			statement.setByte(4, knownStatus.getFileKnownValue());
9246 			connection.executeUpdate(statement);
9247 			resultSet = statement.getGeneratedKeys();
9248 			resultSet.next();
9249 			return new TagName(resultSet.getLong(1), //last_insert_rowid()
9250 					displayName, description, color, knownStatus);
9251 		} catch (SQLException ex) {
9252 			throw new TskCoreException("Error adding row for " + displayName + " tag name to tag_names table", ex);
9253 		} finally {
9254 			closeResultSet(resultSet);
9255 			connection.close();
9256 			releaseSingleUserCaseWriteLock();
9257 		}
9258 	}
9259 
9260 	/**
9261 	 * Inserts a row into the content_tags table in the case database.
9262 	 *
9263 	 * @param content         The content to tag.
9264 	 * @param tagName         The name to use for the tag.
9265 	 * @param comment         A comment to store with the tag.
9266 	 * @param beginByteOffset Designates the beginning of a tagged section.
9267 	 * @param endByteOffset   Designates the end of a tagged section.
9268 	 *
9269 	 * @return A ContentTag data transfer object (DTO) for the new row.
9270 	 *
9271 	 * @throws TskCoreException
9272 	 */
9273 	public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws TskCoreException {
9274 		CaseDbConnection connection = connections.getConnection();
9275 		acquireSingleUserCaseWriteLock();
9276 		ResultSet resultSet = null;
9277 		try {
9278 			Examiner currentExaminer = getCurrentExaminer();
9279 			// INSERT INTO content_tags (obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset, examiner_id) VALUES (?, ?, ?, ?, ?, ?)
9280 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_CONTENT_TAG, Statement.RETURN_GENERATED_KEYS);
9281 			statement.clearParameters();
9282 			statement.setLong(1, content.getId());
9283 			statement.setLong(2, tagName.getId());
9284 			statement.setString(3, comment);
9285 			statement.setLong(4, beginByteOffset);
9286 			statement.setLong(5, endByteOffset);
9287 			statement.setLong(6, currentExaminer.getId());
9288 			connection.executeUpdate(statement);
9289 			resultSet = statement.getGeneratedKeys();
9290 			resultSet.next();
9291 			return new ContentTag(resultSet.getLong(1), //last_insert_rowid()
9292 					content, tagName, comment, beginByteOffset, endByteOffset, currentExaminer.getLoginName());
9293 		} catch (SQLException ex) {
9294 			throw new TskCoreException("Error adding row to content_tags table (obj_id = " + content.getId() + ", tag_name_id = " + tagName.getId() + ")", ex);
9295 		} finally {
9296 			closeResultSet(resultSet);
9297 			connection.close();
9298 			releaseSingleUserCaseWriteLock();
9299 		}
9300 	}
9301 
9302 	/*
9303 	 * Deletes a row from the content_tags table in the case database. @param
9304 	 * tag A ContentTag data transfer object (DTO) for the row to delete.
9305 	 * @throws TskCoreException
9306 	 */
9307 	public void deleteContentTag(ContentTag tag) throws TskCoreException {
9308 		CaseDbConnection connection = connections.getConnection();
9309 		acquireSingleUserCaseWriteLock();
9310 		try {
9311 			// DELETE FROM content_tags WHERE tag_id = ?
9312 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_CONTENT_TAG);
9313 			statement.clearParameters();
9314 			statement.setLong(1, tag.getId());
9315 			connection.executeUpdate(statement);
9316 		} catch (SQLException ex) {
9317 			throw new TskCoreException("Error deleting row from content_tags table (id = " + tag.getId() + ")", ex);
9318 		} finally {
9319 			connection.close();
9320 			releaseSingleUserCaseWriteLock();
9321 		}
9322 	}
9323 
9324 	/**
9325 	 * Selects all of the rows from the content_tags table in the case database.
9326 	 *
9327 	 * @return A list, possibly empty, of ContentTag data transfer objects
9328 	 *         (DTOs) for the rows.
9329 	 *
9330 	 * @throws TskCoreException
9331 	 */
9332 	public List<ContentTag> getAllContentTags() throws TskCoreException {
9333 		CaseDbConnection connection = connections.getConnection();
9334 		acquireSingleUserCaseReadLock();
9335 		ResultSet resultSet = null;
9336 		try {
9337 			// SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9338 			//	FROM content_tags
9339 			//	INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id
9340 			//	LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id
9341 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_CONTENT_TAGS);
9342 			resultSet = connection.executeQuery(statement);
9343 			ArrayList<ContentTag> tags = new ArrayList<ContentTag>();
9344 			while (resultSet.next()) {
9345 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9346 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9347 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));  //NON-NLS
9348 				Content content = getContentById(resultSet.getLong("obj_id")); //NON-NLS
9349 				tags.add(new ContentTag(resultSet.getLong("tag_id"), content, tagName, resultSet.getString("comment"),
9350 						resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset"), resultSet.getString("login_name")));  //NON-NLS
9351 			}
9352 			return tags;
9353 		} catch (SQLException ex) {
9354 			throw new TskCoreException("Error selecting rows from content_tags table", ex);
9355 		} finally {
9356 			closeResultSet(resultSet);
9357 			connection.close();
9358 			releaseSingleUserCaseReadLock();
9359 		}
9360 	}
9361 
9362 	/**
9363 	 * Gets a count of the rows in the content_tags table in the case database
9364 	 * with a specified foreign key into the tag_names table.
9365 	 *
9366 	 * @param tagName A data transfer object (DTO) for the tag name to match.
9367 	 *
9368 	 * @return The count, possibly zero.
9369 	 *
9370 	 * @throws TskCoreException
9371 	 */
9372 	public long getContentTagsCountByTagName(TagName tagName) throws TskCoreException {
9373 		if (tagName.getId() == Tag.ID_NOT_SET) {
9374 			throw new TskCoreException("TagName object is invalid, id not set");
9375 		}
9376 		CaseDbConnection connection = connections.getConnection();
9377 		acquireSingleUserCaseReadLock();
9378 		ResultSet resultSet = null;
9379 		try {
9380 			// SELECT COUNT(*) AS count FROM content_tags WHERE tag_name_id = ?
9381 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_CONTENT_TAGS_BY_TAG_NAME);
9382 			statement.clearParameters();
9383 			statement.setLong(1, tagName.getId());
9384 			resultSet = connection.executeQuery(statement);
9385 			if (resultSet.next()) {
9386 				return resultSet.getLong("count");
9387 			} else {
9388 				throw new TskCoreException("Error getting content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")");
9389 			}
9390 		} catch (SQLException ex) {
9391 			throw new TskCoreException("Error getting content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")", ex);
9392 		} finally {
9393 			closeResultSet(resultSet);
9394 			connection.close();
9395 			releaseSingleUserCaseReadLock();
9396 		}
9397 	}
9398 
9399 	/**
9400 	 * Gets content tags count by tag name, for the given data source
9401 	 *
9402 	 * @param tagName The representation of the desired tag type in the case
9403 	 *                database, which can be obtained by calling getTagNames
9404 	 *                and/or addTagName.
9405 	 *
9406 	 * @param dsObjId data source object id
9407 	 *
9408 	 * @return A count of the content tags with the specified tag name, and for
9409 	 *         the given data source
9410 	 *
9411 	 * @throws TskCoreException If there is an error getting the tags count from
9412 	 *                          the case database.
9413 	 */
9414 	public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException {
9415 
9416 		if (tagName.getId() == Tag.ID_NOT_SET) {
9417 			throw new TskCoreException("TagName object is invalid, id not set");
9418 		}
9419 
9420 		CaseDbConnection connection = connections.getConnection();
9421 		acquireSingleUserCaseReadLock();
9422 		ResultSet resultSet = null;
9423 		try {
9424 			// "SELECT COUNT(*) AS count FROM content_tags as content_tags, tsk_files as tsk_files WHERE content_tags.obj_id = tsk_files.obj_id"
9425 			//		+ " AND content_tags.tag_name_id = ? "
9426 			//		+ " AND tsk_files.data_source_obj_id = ? "
9427 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_CONTENT_TAGS_BY_TAG_NAME_BY_DATASOURCE);
9428 			statement.clearParameters();
9429 			statement.setLong(1, tagName.getId());
9430 			statement.setLong(2, dsObjId);
9431 
9432 			resultSet = connection.executeQuery(statement);
9433 			if (resultSet.next()) {
9434 				return resultSet.getLong("count");
9435 			} else {
9436 				throw new TskCoreException("Error getting content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId);
9437 			}
9438 		} catch (SQLException ex) {
9439 			throw new TskCoreException("Failed to get content_tags row count for  tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex);
9440 		} finally {
9441 			closeResultSet(resultSet);
9442 			connection.close();
9443 			releaseSingleUserCaseReadLock();
9444 		}
9445 	}
9446 
9447 	/**
9448 	 * Selects the rows in the content_tags table in the case database with a
9449 	 * specified tag id.
9450 	 *
9451 	 * @param contentTagID the tag id of the ContentTag to retrieve.
9452 	 *
9453 	 * @return The content tag.
9454 	 *
9455 	 * @throws TskCoreException
9456 	 */
9457 	public ContentTag getContentTagByID(long contentTagID) throws TskCoreException {
9458 
9459 		CaseDbConnection connection = connections.getConnection();
9460 		acquireSingleUserCaseReadLock();
9461 		ResultSet resultSet = null;
9462 		ContentTag tag = null;
9463 		try {
9464 			// SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9465 			//	FROM content_tags
9466 			//	INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id
9467 			//	UTER LEFT JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id
9468 			//	WHERE tag_id = ?
9469 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_CONTENT_TAG_BY_ID);
9470 			statement.clearParameters();
9471 			statement.setLong(1, contentTagID);
9472 			resultSet = connection.executeQuery(statement);
9473 
9474 			while (resultSet.next()) {
9475 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9476 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9477 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));
9478 				tag = new ContentTag(resultSet.getLong("tag_id"), getContentById(resultSet.getLong("obj_id")), tagName,
9479 						resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset"), resultSet.getString("login_name"));
9480 			}
9481 			resultSet.close();
9482 
9483 		} catch (SQLException ex) {
9484 			throw new TskCoreException("Error getting content tag with id = " + contentTagID, ex);
9485 		} finally {
9486 			closeResultSet(resultSet);
9487 			connection.close();
9488 			releaseSingleUserCaseReadLock();
9489 		}
9490 		return tag;
9491 	}
9492 
9493 	/**
9494 	 * Selects the rows in the content_tags table in the case database with a
9495 	 * specified foreign key into the tag_names table.
9496 	 *
9497 	 * @param tagName A data transfer object (DTO) for the tag name to match.
9498 	 *
9499 	 * @return A list, possibly empty, of ContentTag data transfer objects
9500 	 *         (DTOs) for the rows.
9501 	 *
9502 	 * @throws TskCoreException
9503 	 */
9504 	public List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException {
9505 		if (tagName.getId() == Tag.ID_NOT_SET) {
9506 			throw new TskCoreException("TagName object is invalid, id not set");
9507 		}
9508 		CaseDbConnection connection = connections.getConnection();
9509 		acquireSingleUserCaseReadLock();
9510 		ResultSet resultSet = null;
9511 		try {
9512 			// SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tsk_examiners.login_name
9513 			//	FROM content_tags
9514 			//  LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id
9515 			//	WHERE tag_name_id = ?
9516 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_CONTENT_TAGS_BY_TAG_NAME);
9517 			statement.clearParameters();
9518 			statement.setLong(1, tagName.getId());
9519 			resultSet = connection.executeQuery(statement);
9520 			ArrayList<ContentTag> tags = new ArrayList<ContentTag>();
9521 			while (resultSet.next()) {
9522 				ContentTag tag = new ContentTag(resultSet.getLong("tag_id"), getContentById(resultSet.getLong("obj_id")),
9523 						tagName, resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset"), resultSet.getString("login_name"));  //NON-NLS
9524 				tags.add(tag);
9525 			}
9526 			resultSet.close();
9527 			return tags;
9528 		} catch (SQLException ex) {
9529 			throw new TskCoreException("Error getting content_tags rows (tag_name_id = " + tagName.getId() + ")", ex);
9530 		} finally {
9531 			closeResultSet(resultSet);
9532 			connection.close();
9533 			releaseSingleUserCaseReadLock();
9534 		}
9535 	}
9536 
9537 	/**
9538 	 * Gets content tags by tag name, for the given data source.
9539 	 *
9540 	 * @param tagName The tag name of interest.
9541 	 * @param dsObjId data source object id
9542 	 *
9543 	 * @return A list, possibly empty, of the content tags with the specified
9544 	 *         tag name, and for the given data source.
9545 	 *
9546 	 * @throws TskCoreException If there is an error getting the tags from the
9547 	 *                          case database.
9548 	 */
9549 	public List<ContentTag> getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException {
9550 
9551 		CaseDbConnection connection = connections.getConnection();
9552 		acquireSingleUserCaseReadLock();
9553 		ResultSet resultSet = null;
9554 		try {
9555 
9556 			//	SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9557 			//	 FROM content_tags as content_tags, tsk_files as tsk_files
9558 			//	 LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id
9559 			//	 WHERE content_tags.obj_id = tsk_files.obj_id
9560 			//	 AND content_tags.tag_name_id = ?
9561 			//	 AND tsk_files.data_source_obj_id = ?
9562 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_CONTENT_TAGS_BY_TAG_NAME_BY_DATASOURCE);
9563 			statement.clearParameters();
9564 			statement.setLong(1, tagName.getId());
9565 			statement.setLong(2, dsObjId);
9566 			resultSet = connection.executeQuery(statement);
9567 			ArrayList<ContentTag> tags = new ArrayList<ContentTag>();
9568 			while (resultSet.next()) {
9569 				ContentTag tag = new ContentTag(resultSet.getLong("tag_id"), getContentById(resultSet.getLong("obj_id")),
9570 						tagName, resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset"), resultSet.getString("login_name"));  //NON-NLS
9571 				tags.add(tag);
9572 			}
9573 			resultSet.close();
9574 			return tags;
9575 		} catch (SQLException ex) {
9576 			throw new TskCoreException("Failed to get content_tags row count for  tag_name_id = " + tagName.getId() + " data source objID : " + dsObjId, ex);
9577 		} finally {
9578 			closeResultSet(resultSet);
9579 			connection.close();
9580 			releaseSingleUserCaseReadLock();
9581 		}
9582 	}
9583 
9584 	/**
9585 	 * Selects the rows in the content_tags table in the case database with a
9586 	 * specified foreign key into the tsk_objects table.
9587 	 *
9588 	 * @param content A data transfer object (DTO) for the content to match.
9589 	 *
9590 	 * @return A list, possibly empty, of ContentTag data transfer objects
9591 	 *         (DTOs) for the rows.
9592 	 *
9593 	 * @throws TskCoreException
9594 	 */
9595 	public List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException {
9596 		CaseDbConnection connection = connections.getConnection();
9597 		acquireSingleUserCaseReadLock();
9598 		ResultSet resultSet = null;
9599 		try {
9600 			// SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9601 			//	FROM content_tags
9602 			//	INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id
9603 			//	LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id
9604 			//	WHERE content_tags.obj_id = ?
9605 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_CONTENT_TAGS_BY_CONTENT);
9606 			statement.clearParameters();
9607 			statement.setLong(1, content.getId());
9608 			resultSet = connection.executeQuery(statement);
9609 			ArrayList<ContentTag> tags = new ArrayList<ContentTag>();
9610 			while (resultSet.next()) {
9611 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9612 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9613 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));  //NON-NLS
9614 				ContentTag tag = new ContentTag(resultSet.getLong("tag_id"), content, tagName,
9615 						resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset"), resultSet.getString("login_name"));  //NON-NLS
9616 				tags.add(tag);
9617 			}
9618 			return tags;
9619 		} catch (SQLException ex) {
9620 			throw new TskCoreException("Error getting content tags data for content (obj_id = " + content.getId() + ")", ex);
9621 		} finally {
9622 			closeResultSet(resultSet);
9623 			connection.close();
9624 			releaseSingleUserCaseReadLock();
9625 		}
9626 	}
9627 
9628 	/**
9629 	 * Inserts a row into the blackboard_artifact_tags table in the case
9630 	 * database.
9631 	 *
9632 	 * @param artifact The blackboard artifact to tag.
9633 	 * @param tagName  The name to use for the tag.
9634 	 * @param comment  A comment to store with the tag.
9635 	 *
9636 	 * @return A BlackboardArtifactTag data transfer object (DTO) for the new
9637 	 *         row.
9638 	 *
9639 	 * @throws TskCoreException
9640 	 */
9641 	public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException {
9642 		CaseDbConnection connection = connections.getConnection();
9643 		acquireSingleUserCaseWriteLock();
9644 		ResultSet resultSet = null;
9645 		try {
9646 			Examiner currentExaminer = getCurrentExaminer();
9647 			// "INSERT INTO blackboard_artifact_tags (artifact_id, tag_name_id, comment, examiner_id) VALUES (?, ?, ?, ?)"), //NON-NLS
9648 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_ARTIFACT_TAG, Statement.RETURN_GENERATED_KEYS);
9649 			statement.clearParameters();
9650 			statement.setLong(1, artifact.getArtifactID());
9651 			statement.setLong(2, tagName.getId());
9652 			statement.setString(3, comment);
9653 			statement.setLong(4, currentExaminer.getId());
9654 			connection.executeUpdate(statement);
9655 			resultSet = statement.getGeneratedKeys();
9656 			resultSet.next();
9657 			return new BlackboardArtifactTag(resultSet.getLong(1), //last_insert_rowid()
9658 					artifact, getContentById(artifact.getObjectID()), tagName, comment, currentExaminer.getLoginName());
9659 		} catch (SQLException ex) {
9660 			throw new TskCoreException("Error adding row to blackboard_artifact_tags table (obj_id = " + artifact.getArtifactID() + ", tag_name_id = " + tagName.getId() + ")", ex);
9661 		} finally {
9662 			closeResultSet(resultSet);
9663 			connection.close();
9664 			releaseSingleUserCaseWriteLock();
9665 		}
9666 	}
9667 
9668 	/*
9669 	 * Deletes a row from the blackboard_artifact_tags table in the case
9670 	 * database. @param tag A BlackboardArtifactTag data transfer object (DTO)
9671 	 * representing the row to delete. @throws TskCoreException
9672 	 */
9673 	public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException {
9674 		CaseDbConnection connection = connections.getConnection();
9675 		acquireSingleUserCaseWriteLock();
9676 		try {
9677 			// DELETE FROM blackboard_artifact_tags WHERE tag_id = ?
9678 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_ARTIFACT_TAG);
9679 			statement.clearParameters();
9680 			statement.setLong(1, tag.getId());
9681 			connection.executeUpdate(statement);
9682 		} catch (SQLException ex) {
9683 			throw new TskCoreException("Error deleting row from blackboard_artifact_tags table (id = " + tag.getId() + ")", ex);
9684 		} finally {
9685 			connection.close();
9686 			releaseSingleUserCaseWriteLock();
9687 		}
9688 	}
9689 
9690 	/**
9691 	 * Selects all of the rows from the blackboard_artifacts_tags table in the
9692 	 * case database.
9693 	 *
9694 	 * @return A list, possibly empty, of BlackboardArtifactTag data transfer
9695 	 *         objects (DTOs) for the rows.
9696 	 *
9697 	 * @throws TskCoreException
9698 	 */
9699 	public List<BlackboardArtifactTag> getAllBlackboardArtifactTags() throws TskCoreException {
9700 		CaseDbConnection connection = connections.getConnection();
9701 		acquireSingleUserCaseReadLock();
9702 		ResultSet resultSet = null;
9703 		try {
9704 			// SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9705 			//	FROM blackboard_artifact_tags
9706 			//	INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id
9707 			//	LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id
9708 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_TAGS);
9709 			resultSet = connection.executeQuery(statement);
9710 			ArrayList<BlackboardArtifactTag> tags = new ArrayList<BlackboardArtifactTag>();
9711 			while (resultSet.next()) {
9712 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9713 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9714 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));  //NON-NLS
9715 				BlackboardArtifact artifact = getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS
9716 				Content content = getContentById(artifact.getObjectID());
9717 				BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"),
9718 						artifact, content, tagName, resultSet.getString("comment"), resultSet.getString("login_name"));  //NON-NLS
9719 				tags.add(tag);
9720 			}
9721 			return tags;
9722 		} catch (SQLException ex) {
9723 			throw new TskCoreException("Error selecting rows from blackboard_artifact_tags table", ex);
9724 		} finally {
9725 			closeResultSet(resultSet);
9726 			connection.close();
9727 			releaseSingleUserCaseReadLock();
9728 		}
9729 	}
9730 
9731 	/**
9732 	 * Gets a count of the rows in the blackboard_artifact_tags table in the
9733 	 * case database with a specified foreign key into the tag_names table.
9734 	 *
9735 	 * @param tagName A data transfer object (DTO) for the tag name to match.
9736 	 *
9737 	 * @return The count, possibly zero.
9738 	 *
9739 	 * @throws TskCoreException
9740 	 */
9741 	public long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException {
9742 		if (tagName.getId() == Tag.ID_NOT_SET) {
9743 			throw new TskCoreException("TagName object is invalid, id not set");
9744 		}
9745 		CaseDbConnection connection = connections.getConnection();
9746 		acquireSingleUserCaseReadLock();
9747 		ResultSet resultSet = null;
9748 		try {
9749 			// SELECT COUNT(*) AS count FROM blackboard_artifact_tags WHERE tag_name_id = ?
9750 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_ARTIFACTS_BY_TAG_NAME);
9751 			statement.clearParameters();
9752 			statement.setLong(1, tagName.getId());
9753 			resultSet = connection.executeQuery(statement);
9754 			if (resultSet.next()) {
9755 				return resultSet.getLong("count");
9756 			} else {
9757 				throw new TskCoreException("Error getting blackboard_artifact_tags row count for tag name (tag_name_id = " + tagName.getId() + ")");
9758 			}
9759 		} catch (SQLException ex) {
9760 			throw new TskCoreException("Error getting blackboard artifact_content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")", ex);
9761 		} finally {
9762 			closeResultSet(resultSet);
9763 			connection.close();
9764 			releaseSingleUserCaseReadLock();
9765 		}
9766 	}
9767 
9768 	/**
9769 	 * Gets an artifact tags count by tag name, for the given data source.
9770 	 *
9771 	 * @param tagName The representation of the desired tag type in the case
9772 	 *                database, which can be obtained by calling getTagNames
9773 	 *                and/or addTagName.
9774 	 * @param dsObjId data source object id
9775 	 *
9776 	 * @return A count of the artifact tags with the specified tag name, for the
9777 	 *         given data source.
9778 	 *
9779 	 * @throws TskCoreException If there is an error getting the tags count from
9780 	 *                          the case database.
9781 	 */
9782 	public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException {
9783 
9784 		if (tagName.getId() == Tag.ID_NOT_SET) {
9785 			throw new TskCoreException("TagName object is invalid, id not set");
9786 		}
9787 
9788 		CaseDbConnection connection = connections.getConnection();
9789 		acquireSingleUserCaseReadLock();
9790 		ResultSet resultSet = null;
9791 		try {
9792 			// "SELECT COUNT(*) AS count FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts WHERE artifact_tags.artifact_id = arts.artifact_id"
9793 			//    + " AND artifact_tags.tag_name_id = ?"
9794 			//	 + " AND arts.data_source_obj_id =  ? "
9795 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.COUNT_ARTIFACTS_BY_TAG_NAME_BY_DATASOURCE);
9796 			statement.clearParameters();
9797 			statement.setLong(1, tagName.getId());
9798 			statement.setLong(2, dsObjId);
9799 			resultSet = connection.executeQuery(statement);
9800 			if (resultSet.next()) {
9801 				return resultSet.getLong("count");
9802 			} else {
9803 				throw new TskCoreException("Error getting blackboard_artifact_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId);
9804 			}
9805 		} catch (SQLException ex) {
9806 			throw new TskCoreException("Failed to get blackboard_artifact_tags row count for  tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex);
9807 		} finally {
9808 			closeResultSet(resultSet);
9809 			connection.close();
9810 			releaseSingleUserCaseReadLock();
9811 		}
9812 	}
9813 
9814 	/**
9815 	 * Selects the rows in the blackboard_artifacts_tags table in the case
9816 	 * database with a specified foreign key into the tag_names table.
9817 	 *
9818 	 * @param tagName A data transfer object (DTO) for the tag name to match.
9819 	 *
9820 	 * @return A list, possibly empty, of BlackboardArtifactTag data transfer
9821 	 *         objects (DTOs) for the rows.
9822 	 *
9823 	 * @throws TskCoreException
9824 	 */
9825 	public List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException {
9826 		if (tagName.getId() == Tag.ID_NOT_SET) {
9827 			throw new TskCoreException("TagName object is invalid, id not set");
9828 		}
9829 		CaseDbConnection connection = connections.getConnection();
9830 		acquireSingleUserCaseReadLock();
9831 		ResultSet resultSet = null;
9832 		try {
9833 			// SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tsk_examiners.login_name
9834 			//	FROM blackboard_artifact_tags
9835 			//	LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id
9836 			//	WHERE tag_name_id = ?
9837 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_TAGS_BY_TAG_NAME);
9838 			statement.clearParameters();
9839 			statement.setLong(1, tagName.getId());
9840 			resultSet = connection.executeQuery(statement);
9841 			ArrayList<BlackboardArtifactTag> tags = new ArrayList<BlackboardArtifactTag>();
9842 			while (resultSet.next()) {
9843 				BlackboardArtifact artifact = getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS
9844 				Content content = getContentById(artifact.getObjectID());
9845 				BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"),
9846 						artifact, content, tagName, resultSet.getString("comment"), resultSet.getString("login_name"));  //NON-NLS
9847 				tags.add(tag);
9848 			}
9849 			return tags;
9850 		} catch (SQLException ex) {
9851 			throw new TskCoreException("Error getting blackboard artifact tags data (tag_name_id = " + tagName.getId() + ")", ex);
9852 		} finally {
9853 			closeResultSet(resultSet);
9854 			connection.close();
9855 			releaseSingleUserCaseReadLock();
9856 		}
9857 	}
9858 
9859 	/**
9860 	 * Gets artifact tags by tag name, for specified data source.
9861 	 *
9862 	 * @param tagName The representation of the desired tag type in the case
9863 	 *                database, which can be obtained by calling getTagNames
9864 	 *                and/or addTagName.
9865 	 * @param dsObjId data source object id
9866 	 *
9867 	 * @return A list, possibly empty, of the artifact tags with the specified
9868 	 *         tag name, for the specified data source.
9869 	 *
9870 	 * @throws TskCoreException If there is an error getting the tags from the
9871 	 *                          case database.
9872 	 */
9873 	public List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException {
9874 
9875 		if (tagName.getId() == Tag.ID_NOT_SET) {
9876 			throw new TskCoreException("TagName object is invalid, id not set");
9877 		}
9878 
9879 		CaseDbConnection connection = connections.getConnection();
9880 		acquireSingleUserCaseReadLock();
9881 		ResultSet resultSet = null;
9882 		try {
9883 			//	SELECT artifact_tags.tag_id, artifact_tags.artifact_id, artifact_tags.tag_name_id, artifact_tags.comment, arts.obj_id, arts.artifact_obj_id, arts.data_source_obj_id, arts.artifact_type_id, arts.review_status_id, tsk_examiners.login_name
9884 			//	 FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts
9885 			//	 LEFT OUTER JOIN tsk_examiners ON artifact_tags.examiner_id = tsk_examiners.examiner_id
9886 			//	 WHERE artifact_tags.artifact_id = arts.artifact_id
9887 			//	 AND artifact_tags.tag_name_id = ?
9888 			//	 AND arts.data_source_obj_id =  ?
9889 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_TAGS_BY_TAG_NAME_BY_DATASOURCE);
9890 			statement.clearParameters();
9891 			statement.setLong(1, tagName.getId());
9892 			statement.setLong(2, dsObjId);
9893 			resultSet = connection.executeQuery(statement);
9894 			ArrayList<BlackboardArtifactTag> tags = new ArrayList<BlackboardArtifactTag>();
9895 			while (resultSet.next()) {
9896 				BlackboardArtifact artifact = getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS
9897 				Content content = getContentById(artifact.getObjectID());
9898 				BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"),
9899 						artifact, content, tagName, resultSet.getString("comment"), resultSet.getString("login_name"));  //NON-NLS
9900 				tags.add(tag);
9901 			}
9902 			return tags;
9903 		} catch (SQLException ex) {
9904 			throw new TskCoreException("Failed to get blackboard_artifact_tags row count for  tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex);
9905 		} finally {
9906 			closeResultSet(resultSet);
9907 			connection.close();
9908 			releaseSingleUserCaseReadLock();
9909 		}
9910 
9911 	}
9912 
9913 	/**
9914 	 * Selects the row in the blackboard artifact tags table in the case
9915 	 * database with a specified tag id.
9916 	 *
9917 	 * @param artifactTagID the tag id of the BlackboardArtifactTag to retrieve.
9918 	 *
9919 	 * @return the BlackBoardArtifact Tag with the given tag id, or null if no
9920 	 *         such tag could be found
9921 	 *
9922 	 * @throws TskCoreException
9923 	 */
9924 	public BlackboardArtifactTag getBlackboardArtifactTagByID(long artifactTagID) throws TskCoreException {
9925 
9926 		CaseDbConnection connection = connections.getConnection();
9927 		acquireSingleUserCaseReadLock();
9928 		ResultSet resultSet = null;
9929 		BlackboardArtifactTag tag = null;
9930 		try {
9931 			//SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9932 			//	FROM blackboard_artifact_tags
9933 			//	INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id
9934 			//	LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id
9935 			//	WHERE blackboard_artifact_tags.tag_id = ?
9936 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_TAG_BY_ID);
9937 			statement.clearParameters();
9938 			statement.setLong(1, artifactTagID);
9939 			resultSet = connection.executeQuery(statement);
9940 
9941 			while (resultSet.next()) {
9942 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9943 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9944 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));
9945 				BlackboardArtifact artifact = getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS
9946 				Content content = getContentById(artifact.getObjectID());
9947 				tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"),
9948 						artifact, content, tagName, resultSet.getString("comment"), resultSet.getString("login_name"));
9949 			}
9950 			resultSet.close();
9951 
9952 		} catch (SQLException ex) {
9953 			throw new TskCoreException("Error getting blackboard artifact tag with id = " + artifactTagID, ex);
9954 		} finally {
9955 			closeResultSet(resultSet);
9956 			connection.close();
9957 			releaseSingleUserCaseReadLock();
9958 		}
9959 		return tag;
9960 	}
9961 
9962 	/**
9963 	 * Selects the rows in the blackboard_artifacts_tags table in the case
9964 	 * database with a specified foreign key into the blackboard_artifacts
9965 	 * table.
9966 	 *
9967 	 * @param artifact A data transfer object (DTO) for the artifact to match.
9968 	 *
9969 	 * @return A list, possibly empty, of BlackboardArtifactTag data transfer
9970 	 *         objects (DTOs) for the rows.
9971 	 *
9972 	 * @throws TskCoreException
9973 	 */
9974 	public List<BlackboardArtifactTag> getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException {
9975 		CaseDbConnection connection = connections.getConnection();
9976 		acquireSingleUserCaseReadLock();
9977 		ResultSet resultSet = null;
9978 		try {
9979 			//  SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name
9980 			//	FROM blackboard_artifact_tags
9981 			//	INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id
9982 			//	LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id
9983 			//	WHERE blackboard_artifact_tags.artifact_id = ?
9984 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_ARTIFACT_TAGS_BY_ARTIFACT);
9985 			statement.clearParameters();
9986 			statement.setLong(1, artifact.getArtifactID());
9987 			resultSet = connection.executeQuery(statement);
9988 			ArrayList<BlackboardArtifactTag> tags = new ArrayList<BlackboardArtifactTag>();
9989 			while (resultSet.next()) {
9990 				TagName tagName = new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"),
9991 						resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")),
9992 						TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")));  //NON-NLS
9993 				Content content = getContentById(artifact.getObjectID());
9994 				BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"),
9995 						artifact, content, tagName, resultSet.getString("comment"), resultSet.getString("login_name"));  //NON-NLS
9996 				tags.add(tag);
9997 			}
9998 			return tags;
9999 		} catch (SQLException ex) {
10000 			throw new TskCoreException("Error getting blackboard artifact tags data (artifact_id = " + artifact.getArtifactID() + ")", ex);
10001 		} finally {
10002 			closeResultSet(resultSet);
10003 			connection.close();
10004 			releaseSingleUserCaseReadLock();
10005 		}
10006 	}
10007 
10008 	/**
10009 	 * Change the path for an image in the database.
10010 	 *
10011 	 * @param newPath  New path to the image
10012 	 * @param objectId Data source ID of the image
10013 	 *
10014 	 * @throws TskCoreException
10015 	 */
10016 	public void updateImagePath(String newPath, long objectId) throws TskCoreException {
10017 		CaseDbConnection connection = connections.getConnection();
10018 		acquireSingleUserCaseWriteLock();
10019 		try {
10020 			// UPDATE tsk_image_names SET name = ? WHERE obj_id = ?
10021 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_IMAGE_PATH);
10022 			statement.clearParameters();
10023 			statement.setString(1, newPath);
10024 			statement.setLong(2, objectId);
10025 			connection.executeUpdate(statement);
10026 		} catch (SQLException ex) {
10027 			throw new TskCoreException("Error updating image path in database for object " + objectId, ex);
10028 		} finally {
10029 			connection.close();
10030 			releaseSingleUserCaseWriteLock();
10031 		}
10032 	}
10033 
10034 	/**
10035 	 * Inserts a row into the reports table in the case database.
10036 	 *
10037 	 * @param localPath        The path of the report file, must be in the
10038 	 *                         database directory (case directory in Autopsy) or
10039 	 *                         one of its subdirectories.
10040 	 * @param sourceModuleName The name of the module that created the report.
10041 	 * @param reportName       The report name.
10042 	 *
10043 	 * @return A Report object for the new row.
10044 	 *
10045 	 * @throws TskCoreException
10046 	 */
10047 	public Report addReport(String localPath, String sourceModuleName, String reportName) throws TskCoreException {
10048 		return addReport(localPath, sourceModuleName, reportName, null);
10049 	}
10050 
10051 	/**
10052 	 * Inserts a row into the reports table in the case database.
10053 	 *
10054 	 * @param localPath        The path of the report file, must be in the
10055 	 *                         database directory (case directory in Autopsy) or
10056 	 *                         one of its subdirectories.
10057 	 * @param sourceModuleName The name of the module that created the report.
10058 	 * @param reportName       The report name.
10059 	 * @param parent           The Content from which the report was created, if
10060 	 *                         available.
10061 	 *
10062 	 * @return A Report object for the new row.
10063 	 *
10064 	 * @throws TskCoreException
10065 	 */
10066 	public Report addReport(String localPath, String sourceModuleName, String reportName, Content parent) throws TskCoreException {
10067 		// Make sure the local path of the report is in the database directory
10068 		// or one of its subdirectories.
10069 		String relativePath = ""; //NON-NLS
10070 		long createTime = 0;
10071 		String localPathLower = localPath.toLowerCase();
10072 
10073 		if (localPathLower.startsWith("http")) {
10074 			relativePath = localPathLower;
10075 			createTime = System.currentTimeMillis() / 1000;
10076 		} else {
10077 			/*
10078 			 * Note: The following call to .relativize() may be dangerous in
10079 			 * case-sensitive operating systems and should be looked at. For
10080 			 * now, we are simply relativizing the paths as all lower case, then
10081 			 * using the length of the result to pull out the appropriate number
10082 			 * of characters from the localPath String.
10083 			 */
10084 			try {
10085 				String casePathLower = getDbDirPath().toLowerCase();
10086 				int length = new File(casePathLower).toURI().relativize(new File(localPathLower).toURI()).getPath().length();
10087 				relativePath = new File(localPath.substring(localPathLower.length() - length)).getPath();
10088 			} catch (IllegalArgumentException ex) {
10089 				String errorMessage = String.format("Local path %s not in the database directory or one of its subdirectories", localPath);
10090 				throw new TskCoreException(errorMessage, ex);
10091 			}
10092 			try {
10093 				// get its file time
10094 				java.io.File tempFile = new java.io.File(localPath);
10095 				// Convert to UNIX epoch (seconds, not milliseconds).
10096 				createTime = tempFile.lastModified() / 1000;
10097 			} catch (Exception ex) {
10098 				throw new TskCoreException("Could not get create time for report at " + localPath, ex);
10099 			}
10100 		}
10101 
10102 		// Write the report data to the database.
10103 		CaseDbConnection connection = connections.getConnection();
10104 		acquireSingleUserCaseWriteLock();
10105 		ResultSet resultSet = null;
10106 		try {
10107 			// Insert a row for the report into the tsk_objects table.
10108 			// INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)
10109 			long parentObjId = 0;
10110 			if (parent != null) {
10111 				parentObjId = parent.getId();
10112 			}
10113 			long objectId = addObject(parentObjId, TskData.ObjectType.REPORT.getObjectType(), connection);
10114 
10115 			// INSERT INTO reports (obj_id, path, crtime, src_module_name, display_name) VALUES (?, ?, ?, ?, ?)
10116 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_REPORT);
10117 			statement.clearParameters();
10118 			statement.setLong(1, objectId);
10119 			statement.setString(2, relativePath);
10120 			statement.setLong(3, createTime);
10121 			statement.setString(4, sourceModuleName);
10122 			statement.setString(5, reportName);
10123 			connection.executeUpdate(statement);
10124 			return new Report(this, objectId, localPath, createTime, sourceModuleName, reportName, parent);
10125 		} catch (SQLException ex) {
10126 			throw new TskCoreException("Error adding report " + localPath + " to reports table", ex);
10127 		} finally {
10128 			closeResultSet(resultSet);
10129 			connection.close();
10130 			releaseSingleUserCaseWriteLock();
10131 		}
10132 	}
10133 
10134 	/**
10135 	 * Selects all of the rows from the reports table in the case database.
10136 	 *
10137 	 * @return A list, possibly empty, of Report data transfer objects (DTOs)
10138 	 *         for the rows.
10139 	 *
10140 	 * @throws TskCoreException
10141 	 */
10142 	public List<Report> getAllReports() throws TskCoreException {
10143 		CaseDbConnection connection = connections.getConnection();
10144 		acquireSingleUserCaseReadLock();
10145 		ResultSet resultSet = null;
10146 		ResultSet parentResultSet = null;
10147 		PreparedStatement statement = null;
10148 		Statement parentStatement = null;
10149 		try {
10150 			// SELECT * FROM reports
10151 			statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_REPORTS);
10152 			parentStatement = connection.createStatement();
10153 			resultSet = connection.executeQuery(statement);
10154 			ArrayList<Report> reports = new ArrayList<Report>();
10155 			while (resultSet.next()) {
10156 				String localpath = resultSet.getString("path");
10157 				if (localpath.toLowerCase().startsWith("http") == false) {
10158 					// make path absolute
10159 					localpath = Paths.get(getDbDirPath(), localpath).normalize().toString(); //NON-NLS
10160 				}
10161 
10162 				// get the report parent
10163 				Content parent = null;
10164 				long reportId = resultSet.getLong("obj_id"); // NON-NLS
10165 				String parentQuery = String.format("SELECT * FROM tsk_objects WHERE obj_id = %s;", reportId);
10166 				parentResultSet = parentStatement.executeQuery(parentQuery);
10167 				if (parentResultSet.next()) {
10168 					long parentId = parentResultSet.getLong("par_obj_id");	// NON-NLS
10169 					parent = this.getContentById(parentId);
10170 				}
10171 				parentResultSet.close();
10172 
10173 				reports.add(new Report(this,
10174 						reportId,
10175 						localpath,
10176 						resultSet.getLong("crtime"), //NON-NLS
10177 						resultSet.getString("src_module_name"), //NON-NLS
10178 						resultSet.getString("report_name"),
10179 						parent));  //NON-NLS
10180 			}
10181 			return reports;
10182 		} catch (SQLException ex) {
10183 			throw new TskCoreException("Error querying reports table", ex);
10184 		} finally {
10185 			closeResultSet(resultSet);
10186 			closeResultSet(parentResultSet);
10187 			closeStatement(statement);
10188 			closeStatement(parentStatement);
10189 
10190 			connection.close();
10191 			releaseSingleUserCaseReadLock();
10192 		}
10193 	}
10194 
10195 	/**
10196 	 * Get a Report object for the given id.
10197 	 *
10198 	 * @param id
10199 	 *
10200 	 * @return A new Report object for the given id.
10201 	 *
10202 	 * @throws TskCoreException
10203 	 */
10204 	public Report getReportById(long id) throws TskCoreException {
10205 		CaseDbConnection connection = connections.getConnection();
10206 		acquireSingleUserCaseReadLock();
10207 		PreparedStatement statement = null;
10208 		Statement parentStatement = null;
10209 		ResultSet resultSet = null;
10210 		ResultSet parentResultSet = null;
10211 		Report report = null;
10212 		try {
10213 			// SELECT * FROM reports WHERE obj_id = ?
10214 			statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_REPORT_BY_ID);
10215 			parentStatement = connection.createStatement();
10216 			statement.clearParameters();
10217 			statement.setLong(1, id);
10218 			resultSet = connection.executeQuery(statement);
10219 
10220 			if (resultSet.next()) {
10221 				// get the report parent
10222 				Content parent = null;
10223 				String parentQuery = String.format("SELECT * FROM tsk_objects WHERE obj_id = %s;", id);
10224 				parentResultSet = parentStatement.executeQuery(parentQuery);
10225 				if (parentResultSet.next()) {
10226 					long parentId = parentResultSet.getLong("par_obj_id"); // NON-NLS
10227 					parent = this.getContentById(parentId);
10228 				}
10229 
10230 				report = new Report(this, resultSet.getLong("obj_id"), //NON-NLS
10231 						Paths.get(getDbDirPath(), resultSet.getString("path")).normalize().toString(), //NON-NLS
10232 						resultSet.getLong("crtime"), //NON-NLS
10233 						resultSet.getString("src_module_name"), //NON-NLS
10234 						resultSet.getString("report_name"),
10235 						parent);  //NON-NLS
10236 			} else {
10237 				throw new TskCoreException("No report found for id: " + id);
10238 			}
10239 		} catch (SQLException ex) {
10240 			throw new TskCoreException("Error querying reports table for id: " + id, ex);
10241 		} finally {
10242 			closeResultSet(resultSet);
10243 			closeResultSet(parentResultSet);
10244 			closeStatement(statement);
10245 			closeStatement(parentStatement);
10246 			connection.close();
10247 			releaseSingleUserCaseReadLock();
10248 		}
10249 
10250 		return report;
10251 	}
10252 
10253 	/**
10254 	 * Deletes a row from the reports table in the case database.
10255 	 *
10256 	 * @param report A Report data transfer object (DTO) for the row to delete.
10257 	 *
10258 	 * @throws TskCoreException
10259 	 */
10260 	public void deleteReport(Report report) throws TskCoreException {
10261 		CaseDbConnection connection = connections.getConnection();
10262 		acquireSingleUserCaseWriteLock();
10263 		try {
10264 			// DELETE FROM reports WHERE reports.obj_id = ?
10265 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.DELETE_REPORT);
10266 			statement.setLong(1, report.getId());
10267 			connection.executeUpdate(statement);
10268 		} catch (SQLException ex) {
10269 			throw new TskCoreException("Error querying reports table", ex);
10270 		} finally {
10271 			releaseSingleUserCaseWriteLock();
10272 		}
10273 	}
10274 
10275 	static void closeResultSet(ResultSet resultSet) {
10276 		if (resultSet != null) {
10277 			try {
10278 				resultSet.close();
10279 			} catch (SQLException ex) {
10280 				logger.log(Level.SEVERE, "Error closing ResultSet", ex); //NON-NLS
10281 			}
10282 		}
10283 	}
10284 
10285 	static void closeStatement(Statement statement) {
10286 		if (statement != null) {
10287 			try {
10288 				statement.close();
10289 			} catch (SQLException ex) {
10290 				logger.log(Level.SEVERE, "Error closing Statement", ex); //NON-NLS
10291 
10292 			}
10293 		}
10294 	}
10295 
10296 	/**
10297 	 * Sets the end date for the given ingest job
10298 	 *
10299 	 * @param ingestJobId The ingest job to set the end date for
10300 	 * @param endDateTime The end date
10301 	 *
10302 	 * @throws TskCoreException If inserting into the database fails
10303 	 */
10304 	void setIngestJobEndDateTime(long ingestJobId, long endDateTime) throws TskCoreException {
10305 		CaseDbConnection connection = connections.getConnection();
10306 		acquireSingleUserCaseWriteLock();
10307 		try {
10308 			Statement statement = connection.createStatement();
10309 			statement.executeUpdate("UPDATE ingest_jobs SET end_date_time=" + endDateTime + " WHERE ingest_job_id=" + ingestJobId + ";");
10310 		} catch (SQLException ex) {
10311 			throw new TskCoreException("Error updating the end date (ingest_job_id = " + ingestJobId + ".", ex);
10312 		} finally {
10313 			connection.close();
10314 			releaseSingleUserCaseWriteLock();
10315 		}
10316 	}
10317 
10318 	void setIngestJobStatus(long ingestJobId, IngestJobStatusType status) throws TskCoreException {
10319 		CaseDbConnection connection = connections.getConnection();
10320 		acquireSingleUserCaseWriteLock();
10321 		try {
10322 			Statement statement = connection.createStatement();
10323 			statement.executeUpdate("UPDATE ingest_jobs SET status_id=" + status.ordinal() + " WHERE ingest_job_id=" + ingestJobId + ";");
10324 		} catch (SQLException ex) {
10325 			throw new TskCoreException("Error ingest job status (ingest_job_id = " + ingestJobId + ".", ex);
10326 		} finally {
10327 			connection.close();
10328 			releaseSingleUserCaseWriteLock();
10329 		}
10330 	}
10331 
10332 	/**
10333 	 *
10334 	 * @param dataSource    The datasource the ingest job is being run on
10335 	 * @param hostName      The name of the host
10336 	 * @param ingestModules The ingest modules being run during the ingest job.
10337 	 *                      Should be in pipeline order.
10338 	 * @param jobStart      The time the job started
10339 	 * @param jobEnd        The time the job ended
10340 	 * @param status        The ingest job status
10341 	 * @param settingsDir   The directory of the job's settings
10342 	 *
10343 	 * @return An information object representing the ingest job added to the
10344 	 *         database.
10345 	 *
10346 	 * @throws TskCoreException If adding the job to the database fails.
10347 	 */
10348 	public final IngestJobInfo addIngestJob(Content dataSource, String hostName, List<IngestModuleInfo> ingestModules, Date jobStart, Date jobEnd, IngestJobStatusType status, String settingsDir) throws TskCoreException {
10349 		CaseDbConnection connection = connections.getConnection();
10350 		acquireSingleUserCaseWriteLock();
10351 		ResultSet resultSet = null;
10352 		Statement statement;
10353 		try {
10354 			connection.beginTransaction();
10355 			statement = connection.createStatement();
10356 			PreparedStatement insertStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_INGEST_JOB, Statement.RETURN_GENERATED_KEYS);
10357 			insertStatement.setLong(1, dataSource.getId());
10358 			insertStatement.setString(2, hostName);
10359 			insertStatement.setLong(3, jobStart.getTime());
10360 			insertStatement.setLong(4, jobEnd.getTime());
10361 			insertStatement.setInt(5, status.ordinal());
10362 			insertStatement.setString(6, settingsDir);
10363 			connection.executeUpdate(insertStatement);
10364 			resultSet = insertStatement.getGeneratedKeys();
10365 			resultSet.next();
10366 			long id = resultSet.getLong(1); //last_insert_rowid()
10367 			for (int i = 0; i < ingestModules.size(); i++) {
10368 				IngestModuleInfo ingestModule = ingestModules.get(i);
10369 				statement.executeUpdate("INSERT INTO ingest_job_modules (ingest_job_id, ingest_module_id, pipeline_position) "
10370 						+ "VALUES (" + id + ", " + ingestModule.getIngestModuleId() + ", " + i + ");");
10371 			}
10372 			resultSet.close();
10373 			resultSet = null;
10374 			connection.commitTransaction();
10375 			return new IngestJobInfo(id, dataSource.getId(), hostName, jobStart, "", ingestModules, this);
10376 		} catch (SQLException ex) {
10377 			connection.rollbackTransaction();
10378 			throw new TskCoreException("Error adding the ingest job.", ex);
10379 		} finally {
10380 			closeResultSet(resultSet);
10381 			connection.close();
10382 			releaseSingleUserCaseWriteLock();
10383 		}
10384 	}
10385 
10386 	/**
10387 	 * Adds the given ingest module to the database.
10388 	 *
10389 	 * @param displayName      The display name of the module
10390 	 * @param factoryClassName The factory class name of the module.
10391 	 * @param type             The type of the module.
10392 	 * @param version          The version of the module.
10393 	 *
10394 	 * @return An ingest module info object representing the module added to the
10395 	 *         db.
10396 	 *
10397 	 * @throws TskCoreException When the ingest module cannot be added.
10398 	 */
10399 	public final IngestModuleInfo addIngestModule(String displayName, String factoryClassName, IngestModuleType type, String version) throws TskCoreException {
10400 		CaseDbConnection connection = connections.getConnection();
10401 		ResultSet resultSet = null;
10402 		Statement statement = null;
10403 		String uniqueName = factoryClassName + "-" + displayName + "-" + type.toString() + "-" + version;
10404 		acquireSingleUserCaseWriteLock();
10405 		try {
10406 			statement = connection.createStatement();
10407 			resultSet = statement.executeQuery("SELECT * FROM ingest_modules WHERE unique_name = '" + uniqueName + "'");
10408 			if (!resultSet.next()) {
10409 				resultSet.close();
10410 				resultSet = null;
10411 				PreparedStatement insertStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_INGEST_MODULE, Statement.RETURN_GENERATED_KEYS);
10412 				insertStatement.setString(1, displayName);
10413 				insertStatement.setString(2, uniqueName);
10414 				insertStatement.setInt(3, type.ordinal());
10415 				insertStatement.setString(4, version);
10416 				connection.executeUpdate(insertStatement);
10417 				resultSet = statement.getGeneratedKeys();
10418 				resultSet.next();
10419 				long id = resultSet.getLong(1); //last_insert_rowid()
10420 				resultSet.close();
10421 				resultSet = null;
10422 				return new IngestModuleInfo(id, displayName, uniqueName, type, version);
10423 			} else {
10424 				return new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"),
10425 						resultSet.getString("unique_name"), IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version"));
10426 			}
10427 		} catch (SQLException ex) {
10428 			try {
10429 				closeStatement(statement);
10430 				statement = connection.createStatement();
10431 				resultSet = statement.executeQuery("SELECT * FROM ingest_modules WHERE unique_name = '" + uniqueName + "'");
10432 				if (resultSet.next()) {
10433 					return new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"),
10434 							uniqueName, IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version"));
10435 				} else {
10436 					throw new TskCoreException("Couldn't add new module to database.", ex);
10437 				}
10438 			} catch (SQLException ex1) {
10439 				throw new TskCoreException("Couldn't add new module to database.", ex1);
10440 			}
10441 		} finally {
10442 			closeResultSet(resultSet);
10443 			closeStatement(statement);
10444 			connection.close();
10445 			releaseSingleUserCaseWriteLock();
10446 		}
10447 	}
10448 
10449 	/**
10450 	 * Gets all of the ingest jobs that have been run.
10451 	 *
10452 	 * @return The information about the ingest jobs that have been run
10453 	 *
10454 	 * @throws TskCoreException If there is a problem getting the ingest jobs
10455 	 */
10456 	public final List<IngestJobInfo> getIngestJobs() throws TskCoreException {
10457 		CaseDbConnection connection = connections.getConnection();
10458 		ResultSet resultSet = null;
10459 		Statement statement = null;
10460 		List<IngestJobInfo> ingestJobs = new ArrayList<IngestJobInfo>();
10461 		acquireSingleUserCaseReadLock();
10462 		try {
10463 			statement = connection.createStatement();
10464 			resultSet = statement.executeQuery("SELECT * FROM ingest_jobs");
10465 			while (resultSet.next()) {
10466 				ingestJobs.add(new IngestJobInfo(resultSet.getInt("ingest_job_id"), resultSet.getLong("obj_id"),
10467 						resultSet.getString("host_name"), new Date(resultSet.getLong("start_date_time")),
10468 						new Date(resultSet.getLong("end_date_time")), IngestJobStatusType.fromID(resultSet.getInt("status_id")),
10469 						resultSet.getString("settings_dir"), this.getIngestModules(resultSet.getInt("ingest_job_id"), connection), this));
10470 			}
10471 			return ingestJobs;
10472 		} catch (SQLException ex) {
10473 			throw new TskCoreException("Couldn't get the ingest jobs.", ex);
10474 		} finally {
10475 			closeResultSet(resultSet);
10476 			closeStatement(statement);
10477 			connection.close();
10478 			releaseSingleUserCaseReadLock();
10479 		}
10480 	}
10481 
10482 	/**
10483 	 * Gets the ingest modules associated with the ingest job
10484 	 *
10485 	 * @param ingestJobId The id of the ingest job to get ingest modules for
10486 	 * @param connection  The database connection
10487 	 *
10488 	 * @return The ingest modules of the job
10489 	 *
10490 	 * @throws SQLException If it fails to get the modules from the db.
10491 	 */
10492 	private List<IngestModuleInfo> getIngestModules(int ingestJobId, CaseDbConnection connection) throws SQLException {
10493 		ResultSet resultSet = null;
10494 		Statement statement = null;
10495 		List<IngestModuleInfo> ingestModules = new ArrayList<IngestModuleInfo>();
10496 		acquireSingleUserCaseReadLock();
10497 		try {
10498 			statement = connection.createStatement();
10499 			resultSet = statement.executeQuery("SELECT ingest_job_modules.ingest_module_id AS ingest_module_id, "
10500 					+ "ingest_job_modules.pipeline_position AS pipeline_position, "
10501 					+ "ingest_modules.display_name AS display_name, ingest_modules.unique_name AS unique_name, "
10502 					+ "ingest_modules.type_id AS type_id, ingest_modules.version AS version "
10503 					+ "FROM ingest_job_modules, ingest_modules "
10504 					+ "WHERE ingest_job_modules.ingest_job_id = " + ingestJobId + " "
10505 					+ "AND ingest_modules.ingest_module_id = ingest_job_modules.ingest_module_id "
10506 					+ "ORDER BY (ingest_job_modules.pipeline_position);");
10507 			while (resultSet.next()) {
10508 				ingestModules.add(new IngestModuleInfo(resultSet.getInt("ingest_module_id"), resultSet.getString("display_name"),
10509 						resultSet.getString("unique_name"), IngestModuleType.fromID(resultSet.getInt("type_id")), resultSet.getString("version")));
10510 			}
10511 			return ingestModules;
10512 		} finally {
10513 			closeResultSet(resultSet);
10514 			closeStatement(statement);
10515 			releaseSingleUserCaseReadLock();
10516 
10517 		}
10518 	}
10519 
10520 	/**
10521 	 * Stores a pair of object ID and its type
10522 	 */
10523 	static class ObjectInfo {
10524 
10525 		private long id;
10526 		private TskData.ObjectType type;
10527 
10528 		ObjectInfo(long id, ObjectType type) {
10529 			this.id = id;
10530 			this.type = type;
10531 		}
10532 
10533 		long getId() {
10534 			return id;
10535 		}
10536 
10537 		TskData.ObjectType getType() {
10538 			return type;
10539 		}
10540 	}
10541 
10542 	private interface DbCommand {
10543 
10544 		void execute() throws SQLException;
10545 	}
10546 
10547 	private enum PREPARED_STATEMENT {
10548 
10549 		SELECT_ARTIFACTS_BY_TYPE("SELECT artifact_id, obj_id FROM blackboard_artifacts " //NON-NLS
10550 				+ "WHERE artifact_type_id = ?"), //NON-NLS
10551 		COUNT_ARTIFACTS_OF_TYPE("SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE artifact_type_id = ? AND review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID()), //NON-NLS
10552 		COUNT_ARTIFACTS_FROM_SOURCE("SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE obj_id = ? AND review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID()), //NON-NLS
10553 		COUNT_ARTIFACTS_BY_SOURCE_AND_TYPE("SELECT COUNT(*) AS count FROM blackboard_artifacts WHERE obj_id = ? AND artifact_type_id = ? AND review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID()), //NON-NLS
10554 		SELECT_FILES_BY_PARENT("SELECT tsk_files.* " //NON-NLS
10555 				+ "FROM tsk_objects INNER JOIN tsk_files " //NON-NLS
10556 				+ "ON tsk_objects.obj_id=tsk_files.obj_id " //NON-NLS
10557 				+ "WHERE (tsk_objects.par_obj_id = ? ) " //NON-NLS
10558 				+ "ORDER BY tsk_files.meta_type DESC, LOWER(tsk_files.name)"), //NON-NLS
10559 		SELECT_FILES_BY_PARENT_AND_TYPE("SELECT tsk_files.* " //NON-NLS
10560 				+ "FROM tsk_objects INNER JOIN tsk_files " //NON-NLS
10561 				+ "ON tsk_objects.obj_id=tsk_files.obj_id " //NON-NLS
10562 				+ "WHERE (tsk_objects.par_obj_id = ? AND tsk_files.type = ? ) " //NON-NLS
10563 				+ "ORDER BY tsk_files.dir_type, LOWER(tsk_files.name)"), //NON-NLS
10564 		SELECT_FILE_IDS_BY_PARENT("SELECT tsk_files.obj_id AS obj_id " //NON-NLS
10565 				+ "FROM tsk_objects INNER JOIN tsk_files " //NON-NLS
10566 				+ "ON tsk_objects.obj_id=tsk_files.obj_id " //NON-NLS
10567 				+ "WHERE (tsk_objects.par_obj_id = ?)"), //NON-NLS
10568 		SELECT_FILE_IDS_BY_PARENT_AND_TYPE("SELECT tsk_files.obj_id AS obj_id " //NON-NLS
10569 				+ "FROM tsk_objects INNER JOIN tsk_files " //NON-NLS
10570 				+ "ON tsk_objects.obj_id=tsk_files.obj_id " //NON-NLS
10571 				+ "WHERE (tsk_objects.par_obj_id = ? " //NON-NLS
10572 				+ "AND tsk_files.type = ? )"), //NON-NLS
10573 		SELECT_FILE_BY_ID("SELECT * FROM tsk_files WHERE obj_id = ? LIMIT 1"), //NON-NLS
10574 		SELECT_ARTIFACT_BY_ARTIFACT_OBJ_ID("SELECT * FROM blackboard_artifacts WHERE artifact_obj_id = ? LIMIT 1"),
10575 		SELECT_ARTIFACT_BY_ARTIFACT_ID("SELECT * FROM blackboard_artifacts WHERE artifact_id = ? LIMIT 1"),
10576 		INSERT_ARTIFACT("INSERT INTO blackboard_artifacts (artifact_id, obj_id, artifact_obj_id, data_source_obj_id, artifact_type_id, review_status_id) " //NON-NLS
10577 				+ "VALUES (?, ?, ?, ?, ?," + BlackboardArtifact.ReviewStatus.UNDECIDED.getID() + ")"), //NON-NLS
10578 		POSTGRESQL_INSERT_ARTIFACT("INSERT INTO blackboard_artifacts (artifact_id, obj_id, artifact_obj_id, data_source_obj_id, artifact_type_id, review_status_id) " //NON-NLS
10579 				+ "VALUES (DEFAULT, ?, ?, ?, ?," + BlackboardArtifact.ReviewStatus.UNDECIDED.getID() + ")"), //NON-NLS
10580 		INSERT_STRING_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_text) " //NON-NLS
10581 				+ "VALUES (?,?,?,?,?,?,?)"), //NON-NLS
10582 		INSERT_BYTE_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_byte) " //NON-NLS
10583 				+ "VALUES (?,?,?,?,?,?,?)"), //NON-NLS
10584 		INSERT_INT_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_int32) " //NON-NLS
10585 				+ "VALUES (?,?,?,?,?,?,?)"), //NON-NLS
10586 		INSERT_LONG_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_int64) " //NON-NLS
10587 				+ "VALUES (?,?,?,?,?,?,?)"), //NON-NLS
10588 		INSERT_DOUBLE_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_double) " //NON-NLS
10589 				+ "VALUES (?,?,?,?,?,?,?)"), //NON-NLS
10590 		SELECT_FILES_BY_DATA_SOURCE_AND_NAME("SELECT * FROM tsk_files WHERE LOWER(name) LIKE LOWER(?) AND LOWER(name) NOT LIKE LOWER('%journal%') AND data_source_obj_id = ?"), //NON-NLS
10591 		SELECT_FILES_BY_DATA_SOURCE_AND_PARENT_PATH_AND_NAME("SELECT * FROM tsk_files WHERE LOWER(name) LIKE LOWER(?) AND LOWER(name) NOT LIKE LOWER('%journal%') AND LOWER(parent_path) LIKE LOWER(?) AND data_source_obj_id = ?"), //NON-NLS
10592 		UPDATE_FILE_MD5("UPDATE tsk_files SET md5 = ? WHERE obj_id = ?"), //NON-NLS
10593 		UPDATE_IMAGE_MD5("UPDATE tsk_image_info SET md5 = ? WHERE obj_id = ?"), //NON-NLS
10594 		UPDATE_IMAGE_SHA1("UPDATE tsk_image_info SET sha1 = ? WHERE obj_id = ?"), //NON-NLS
10595 		UPDATE_IMAGE_SHA256("UPDATE tsk_image_info SET sha256 = ? WHERE obj_id = ?"), //NON-NLS
10596 		SELECT_IMAGE_MD5("SELECT md5 FROM tsk_image_info WHERE obj_id = ?"), //NON-NLS
10597 		SELECT_IMAGE_SHA1("SELECT sha1 FROM tsk_image_info WHERE obj_id = ?"), //NON-NLS
10598 		SELECT_IMAGE_SHA256("SELECT sha256 FROM tsk_image_info WHERE obj_id = ?"), //NON-NLS
10599 		UPDATE_ACQUISITION_DETAILS("UPDATE data_source_info SET acquisition_details = ? WHERE obj_id = ?"), //NON-NLS
10600 		SELECT_ACQUISITION_DETAILS("SELECT acquisition_details FROM data_source_info WHERE obj_id = ?"), //NON-NLS
10601 		SELECT_LOCAL_PATH_FOR_FILE("SELECT path FROM tsk_files_path WHERE obj_id = ?"), //NON-NLS
10602 		SELECT_ENCODING_FOR_FILE("SELECT encoding_type FROM tsk_files_path WHERE obj_id = ?"), // NON-NLS
10603 		SELECT_LOCAL_PATH_AND_ENCODING_FOR_FILE("SELECT path, encoding_type FROM tsk_files_path WHERE obj_id = ?"), // NON_NLS
10604 		SELECT_PATH_FOR_FILE("SELECT parent_path FROM tsk_files WHERE obj_id = ?"), //NON-NLS
10605 		SELECT_FILE_NAME("SELECT name FROM tsk_files WHERE obj_id = ?"), //NON-NLS
10606 		SELECT_DERIVED_FILE("SELECT derived_id, rederive FROM tsk_files_derived WHERE obj_id = ?"), //NON-NLS
10607 		SELECT_FILE_DERIVATION_METHOD("SELECT tool_name, tool_version, other FROM tsk_files_derived_method WHERE derived_id = ?"), //NON-NLS
10608 		SELECT_MAX_OBJECT_ID("SELECT MAX(obj_id) AS max_obj_id FROM tsk_objects"), //NON-NLS
10609 		INSERT_OBJECT("INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)"), //NON-NLS
10610 		INSERT_FILE("INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension) " //NON-NLS
10611 				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), //NON-NLS
10612 		INSERT_FILE_SYSTEM_FILE("INSERT INTO tsk_files(obj_id, fs_obj_id, data_source_obj_id, attr_type, attr_id, name, meta_addr, meta_seq, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, parent_path, extension)"
10613 				+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), // NON-NLS
10614 		UPDATE_DERIVED_FILE("UPDATE tsk_files SET type = ?, dir_type = ?, meta_type = ?, dir_flags = ?,  meta_flags = ?, size= ?, ctime= ?, crtime= ?, atime= ?, mtime= ?, mime_type = ?  "
10615 				+ "WHERE obj_id = ?"), //NON-NLS
10616 		INSERT_LAYOUT_FILE("INSERT INTO tsk_file_layout (obj_id, byte_start, byte_len, sequence) " //NON-NLS
10617 				+ "VALUES (?, ?, ?, ?)"), //NON-NLS
10618 		INSERT_LOCAL_PATH("INSERT INTO tsk_files_path (obj_id, path, encoding_type) VALUES (?, ?, ?)"), //NON-NLS
10619 		UPDATE_LOCAL_PATH("UPDATE tsk_files_path SET path = ?, encoding_type = ? WHERE obj_id = ?"), //NON-NLS
10620 		COUNT_CHILD_OBJECTS_BY_PARENT("SELECT COUNT(obj_id) AS count FROM tsk_objects WHERE par_obj_id = ?"), //NON-NLS
10621 		SELECT_FILE_SYSTEM_BY_OBJECT("SELECT fs_obj_id from tsk_files WHERE obj_id=?"), //NON-NLS
10622 		SELECT_TAG_NAMES("SELECT * FROM tag_names"), //NON-NLS
10623 		SELECT_TAG_NAMES_IN_USE("SELECT * FROM tag_names " //NON-NLS
10624 				+ "WHERE tag_name_id IN " //NON-NLS
10625 				+ "(SELECT tag_name_id from content_tags UNION SELECT tag_name_id FROM blackboard_artifact_tags)"), //NON-NLS
10626 		SELECT_TAG_NAMES_IN_USE_BY_DATASOURCE("SELECT * FROM tag_names "
10627 				+ "WHERE tag_name_id IN "
10628 				+ "( SELECT content_tags.tag_name_id as tag_name_id "
10629 				+ "FROM content_tags as content_tags, tsk_files as tsk_files"
10630 				+ " WHERE content_tags.obj_id = tsk_files.obj_id"
10631 				+ " AND tsk_files.data_source_obj_id =  ?"
10632 				+ " UNION "
10633 				+ "SELECT artifact_tags.tag_name_id as tag_name_id "
10634 				+ " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts "
10635 				+ " WHERE artifact_tags.artifact_id = arts.artifact_id"
10636 				+ " AND arts.data_source_obj_id =  ?"
10637 				+ " )"),
10638 		INSERT_TAG_NAME("INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?)"), //NON-NLS
10639 		INSERT_CONTENT_TAG("INSERT INTO content_tags (obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset, examiner_id) VALUES (?, ?, ?, ?, ?, ?)"), //NON-NLS
10640 		DELETE_CONTENT_TAG("DELETE FROM content_tags WHERE tag_id = ?"), //NON-NLS
10641 		COUNT_CONTENT_TAGS_BY_TAG_NAME("SELECT COUNT(*) AS count FROM content_tags WHERE tag_name_id = ?"), //NON-NLS
10642 		COUNT_CONTENT_TAGS_BY_TAG_NAME_BY_DATASOURCE(
10643 				"SELECT COUNT(*) AS count FROM content_tags as content_tags, tsk_files as tsk_files WHERE content_tags.obj_id = tsk_files.obj_id"
10644 				+ " AND content_tags.tag_name_id = ? "
10645 				+ " AND tsk_files.data_source_obj_id = ? "
10646 		),
10647 		SELECT_CONTENT_TAGS("SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10648 				+ "FROM content_tags "
10649 				+ "INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id "
10650 				+ "LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id"), //NON-NLS
10651 		SELECT_CONTENT_TAGS_BY_TAG_NAME("SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tsk_examiners.login_name "
10652 				+ "FROM content_tags "
10653 				+ "LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id "
10654 				+ "WHERE tag_name_id = ?"), //NON-NLS
10655 		SELECT_CONTENT_TAGS_BY_TAG_NAME_BY_DATASOURCE("SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10656 				+ "FROM content_tags as content_tags, tsk_files as tsk_files, tag_names as tag_names "
10657 				+ "LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id "
10658 				+ "WHERE content_tags.obj_id = tsk_files.obj_id"
10659 				+ " AND content_tags.tag_name_id = tag_names.tag_name_id"
10660 				+ " AND content_tags.tag_name_id = ?"
10661 				+ " AND tsk_files.data_source_obj_id = ? "),
10662 		SELECT_CONTENT_TAG_BY_ID("SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10663 				+ "FROM content_tags "
10664 				+ "INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id "
10665 				+ "LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id "
10666 				+ "WHERE tag_id = ?"), //NON-NLS
10667 		SELECT_CONTENT_TAGS_BY_CONTENT("SELECT content_tags.tag_id, content_tags.obj_id, content_tags.tag_name_id, content_tags.comment, content_tags.begin_byte_offset, content_tags.end_byte_offset, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10668 				+ "FROM content_tags "
10669 				+ "INNER JOIN tag_names ON content_tags.tag_name_id = tag_names.tag_name_id "
10670 				+ "LEFT OUTER JOIN tsk_examiners ON content_tags.examiner_id = tsk_examiners.examiner_id "
10671 				+ "WHERE content_tags.obj_id = ?"), //NON-NLS
10672 		INSERT_ARTIFACT_TAG("INSERT INTO blackboard_artifact_tags (artifact_id, tag_name_id, comment, examiner_id) "
10673 				+ "VALUES (?, ?, ?, ?)"), //NON-NLS
10674 		DELETE_ARTIFACT_TAG("DELETE FROM blackboard_artifact_tags WHERE tag_id = ?"), //NON-NLS
10675 		SELECT_ARTIFACT_TAGS("SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10676 				+ "FROM blackboard_artifact_tags "
10677 				+ "INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id "
10678 				+ "LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id"), //NON-NLS
10679 		COUNT_ARTIFACTS_BY_TAG_NAME("SELECT COUNT(*) AS count FROM blackboard_artifact_tags WHERE tag_name_id = ?"), //NON-NLS
10680 		COUNT_ARTIFACTS_BY_TAG_NAME_BY_DATASOURCE("SELECT COUNT(*) AS count FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts WHERE artifact_tags.artifact_id = arts.artifact_id"
10681 				+ " AND artifact_tags.tag_name_id = ?"
10682 				+ " AND arts.data_source_obj_id =  ? "),
10683 		SELECT_ARTIFACT_TAGS_BY_TAG_NAME("SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tsk_examiners.login_name "
10684 				+ "FROM blackboard_artifact_tags "
10685 				+ "LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id "
10686 				+ "WHERE tag_name_id = ?"), //NON-NLS
10687 		SELECT_ARTIFACT_TAGS_BY_TAG_NAME_BY_DATASOURCE("SELECT artifact_tags.tag_id, artifact_tags.artifact_id, artifact_tags.tag_name_id, artifact_tags.comment, arts.obj_id, arts.artifact_obj_id, arts.data_source_obj_id, arts.artifact_type_id, arts.review_status_id, tsk_examiners.login_name "
10688 				+ "FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts "
10689 				+ "LEFT OUTER JOIN tsk_examiners ON artifact_tags.examiner_id = tsk_examiners.examiner_id "
10690 				+ "WHERE artifact_tags.artifact_id = arts.artifact_id"
10691 				+ " AND artifact_tags.tag_name_id = ? "
10692 				+ " AND arts.data_source_obj_id =  ? "),
10693 		SELECT_ARTIFACT_TAG_BY_ID("SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10694 				+ "FROM blackboard_artifact_tags "
10695 				+ "INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id  "
10696 				+ "LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id "
10697 				+ "WHERE blackboard_artifact_tags.tag_id = ?"), //NON-NLS
10698 		SELECT_ARTIFACT_TAGS_BY_ARTIFACT("SELECT blackboard_artifact_tags.tag_id, blackboard_artifact_tags.artifact_id, blackboard_artifact_tags.tag_name_id, blackboard_artifact_tags.comment, tag_names.display_name, tag_names.description, tag_names.color, tag_names.knownStatus, tsk_examiners.login_name "
10699 				+ "FROM blackboard_artifact_tags "
10700 				+ "INNER JOIN tag_names ON blackboard_artifact_tags.tag_name_id = tag_names.tag_name_id "
10701 				+ "LEFT OUTER JOIN tsk_examiners ON blackboard_artifact_tags.examiner_id = tsk_examiners.examiner_id "
10702 				+ "WHERE blackboard_artifact_tags.artifact_id = ?"), //NON-NLS
10703 		SELECT_REPORTS("SELECT * FROM reports"), //NON-NLS
10704 		SELECT_REPORT_BY_ID("SELECT * FROM reports WHERE obj_id = ?"), //NON-NLS
10705 		INSERT_REPORT("INSERT INTO reports (obj_id, path, crtime, src_module_name, report_name) VALUES (?, ?, ?, ?, ?)"), //NON-NLS
10706 		DELETE_REPORT("DELETE FROM reports WHERE reports.obj_id = ?"), //NON-NLS
10707 		INSERT_INGEST_JOB("INSERT INTO ingest_jobs (obj_id, host_name, start_date_time, end_date_time, status_id, settings_dir) VALUES (?, ?, ?, ?, ?, ?)"), //NON-NLS
10708 		INSERT_INGEST_MODULE("INSERT INTO ingest_modules (display_name, unique_name, type_id, version) VALUES(?, ?, ?, ?)"), //NON-NLS
10709 		SELECT_ATTR_BY_VALUE_BYTE("SELECT source FROM blackboard_attributes WHERE artifact_id = ? AND attribute_type_id = ? AND value_type = 4 AND value_byte = ?"), //NON-NLS
10710 		UPDATE_ATTR_BY_VALUE_BYTE("UPDATE blackboard_attributes SET source = ? WHERE artifact_id = ? AND attribute_type_id = ? AND value_type = 4 AND value_byte = ?"), //NON-NLS
10711 		UPDATE_IMAGE_PATH("UPDATE tsk_image_names SET name = ? WHERE obj_id = ?"), // NON-NLS
10712 		SELECT_ARTIFACT_OBJECTIDS_BY_PARENT("SELECT blackboard_artifacts.artifact_obj_id AS artifact_obj_id " //NON-NLS
10713 				+ "FROM tsk_objects INNER JOIN blackboard_artifacts " //NON-NLS
10714 				+ "ON tsk_objects.obj_id=blackboard_artifacts.obj_id " //NON-NLS
10715 				+ "WHERE (tsk_objects.par_obj_id = ?)"),
10716 		INSERT_OR_UPDATE_TAG_NAME_POSTGRES("INSERT INTO tag_names (display_name, description, color, knownStatus) VALUES (?, ?, ?, ?) ON CONFLICT (display_name) DO UPDATE SET description = ?, color = ?, knownStatus = ?"),
10717 		INSERT_OR_UPDATE_TAG_NAME_SQLITE("WITH new (display_name, description, color, knownStatus) "
10718 				+ "AS ( VALUES(?, ?, ?, ?)) INSERT OR REPLACE INTO tag_names "
10719 				+ "(tag_name_id, display_name, description, color, knownStatus) "
10720 				+ "SELECT old.tag_name_id, new.display_name, new.description, new.color, new.knownStatus "
10721 				+ "FROM new LEFT JOIN tag_names AS old ON new.display_name = old.display_name"),
10722 		SELECT_EXAMINER_BY_ID("SELECT * FROM tsk_examiners WHERE examiner_id = ?"),
10723 		SELECT_EXAMINER_BY_LOGIN_NAME("SELECT * FROM tsk_examiners WHERE login_name = ?"),
10724 		UPDATE_FILE_NAME("UPDATE tsk_files SET name = ? WHERE obj_id = ?"),
10725 		UPDATE_IMAGE_NAME("UPDATE tsk_image_info SET display_name = ? WHERE obj_id = ?"),
10726 		DELETE_IMAGE_NAME("DELETE FROM tsk_image_names WHERE obj_id = ?"),
10727 		INSERT_IMAGE_NAME("INSERT INTO tsk_image_names (obj_id, name, sequence) VALUES (?, ?, ?)"),
10728 		INSERT_IMAGE_INFO("INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, sha1, sha256, display_name)"
10729 				+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
10730 		INSERT_DATA_SOURCE_INFO("INSERT INTO data_source_info (obj_id, device_id, time_zone) VALUES (?, ?, ?)"),
10731 		INSERT_VS_INFO("INSERT INTO tsk_vs_info (obj_id, vs_type, img_offset, block_size) VALUES (?, ?, ?, ?)"),
10732 		INSERT_VS_PART_SQLITE("INSERT INTO tsk_vs_parts (obj_id, addr, start, length, desc, flags) VALUES (?, ?, ?, ?, ?, ?)"),
10733 		INSERT_VS_PART_POSTGRESQL("INSERT INTO tsk_vs_parts (obj_id, addr, start, length, descr, flags) VALUES (?, ?, ?, ?, ?, ?)"),
10734 		INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)"
10735 				+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
10736 
10737 		private final String sql;
10738 
10739 		private PREPARED_STATEMENT(String sql) {
10740 			this.sql = sql;
10741 		}
10742 
10743 		String getSQL() {
10744 			return sql;
10745 		}
10746 	}
10747 
10748 	/**
10749 	 * A class for the connection pool. This class will hand out connections of
10750 	 * the appropriate type based on the subclass that is calling
10751 	 * getPooledConnection();
10752 	 */
10753 	abstract private class ConnectionPool {
10754 
10755 		private PooledDataSource pooledDataSource;
10756 
10757 		public ConnectionPool() {
10758 			pooledDataSource = null;
10759 		}
10760 
10761 		CaseDbConnection getConnection() throws TskCoreException {
10762 			if (pooledDataSource == null) {
10763 				throw new TskCoreException("Error getting case database connection - case is closed");
10764 			}
10765 			try {
10766 				return getPooledConnection();
10767 			} catch (SQLException exp) {
10768 				throw new TskCoreException(exp.getMessage());
10769 			}
10770 		}
10771 
10772 		void close() throws TskCoreException {
10773 			if (pooledDataSource != null) {
10774 				try {
10775 					pooledDataSource.close();
10776 				} catch (SQLException exp) {
10777 					throw new TskCoreException(exp.getMessage());
10778 				} finally {
10779 					pooledDataSource = null;
10780 				}
10781 			}
10782 		}
10783 
10784 		abstract CaseDbConnection getPooledConnection() throws SQLException;
10785 
10786 		public PooledDataSource getPooledDataSource() {
10787 			return pooledDataSource;
10788 		}
10789 
10790 		public void setPooledDataSource(PooledDataSource pooledDataSource) {
10791 			this.pooledDataSource = pooledDataSource;
10792 		}
10793 	}
10794 
10795 	/**
10796 	 * Handles the initial setup of SQLite database connections, as well as
10797 	 * overriding getPooledConnection()
10798 	 */
10799 	private final class SQLiteConnections extends ConnectionPool {
10800 
10801 		private final Map<String, String> configurationOverrides = new HashMap<String, String>();
10802 
10803 		SQLiteConnections(String dbPath) throws SQLException {
10804 			configurationOverrides.put("acquireIncrement", "2");
10805 			configurationOverrides.put("initialPoolSize", "5");
10806 			configurationOverrides.put("minPoolSize", "5");
10807 			/*
10808 			 * NOTE: max pool size and max statements are related. If you
10809 			 * increase max pool size, then also increase statements.
10810 			 */
10811 			configurationOverrides.put("maxPoolSize", "20");
10812 			configurationOverrides.put("maxStatements", "200");
10813 			configurationOverrides.put("maxStatementsPerConnection", "20");
10814 
10815 			SQLiteConfig config = new SQLiteConfig();
10816 			config.setSynchronous(SQLiteConfig.SynchronousMode.OFF); // Reduce I/O operations, we have no OS crash recovery anyway.
10817 			config.setReadUncommited(true);
10818 			config.enforceForeignKeys(true); // Enforce foreign key constraints.
10819 			SQLiteDataSource unpooled = new SQLiteDataSource(config);
10820 			unpooled.setUrl("jdbc:sqlite:" + dbPath);
10821 			setPooledDataSource((PooledDataSource) DataSources.pooledDataSource(unpooled, configurationOverrides));
10822 		}
10823 
10824 		@Override
10825 		public CaseDbConnection getPooledConnection() throws SQLException {
10826 			return new SQLiteConnection(getPooledDataSource().getConnection());
10827 		}
10828 	}
10829 
10830 	/**
10831 	 * Handles the initial setup of PostgreSQL database connections, as well as
10832 	 * overriding getPooledConnection()
10833 	 */
10834 	private final class PostgreSQLConnections extends ConnectionPool {
10835 
10836 		PostgreSQLConnections(String host, int port, String dbName, String userName, String password) throws PropertyVetoException, UnsupportedEncodingException {
10837 			ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
10838 			comboPooledDataSource.setDriverClass("org.postgresql.Driver"); //loads the jdbc driver
10839 			comboPooledDataSource.setJdbcUrl("jdbc:postgresql://" + host + ":" + port + "/"
10840 					+ URLEncoder.encode(dbName, StandardCharsets.UTF_8.toString()));
10841 			comboPooledDataSource.setUser(userName);
10842 			comboPooledDataSource.setPassword(password);
10843 			comboPooledDataSource.setAcquireIncrement(2);
10844 			comboPooledDataSource.setInitialPoolSize(5);
10845 			comboPooledDataSource.setMinPoolSize(5);
10846 			/*
10847 			 * NOTE: max pool size and max statements are related. If you
10848 			 * increase max pool size, then also increase statements.
10849 			 */
10850 			comboPooledDataSource.setMaxPoolSize(20);
10851 			comboPooledDataSource.setMaxStatements(200);
10852 			comboPooledDataSource.setMaxStatementsPerConnection(20);
10853 			setPooledDataSource(comboPooledDataSource);
10854 		}
10855 
10856 		@Override
10857 		public CaseDbConnection getPooledConnection() throws SQLException {
10858 			return new PostgreSQLConnection(getPooledDataSource().getConnection());
10859 		}
10860 	}
10861 
10862 	/**
10863 	 * An abstract base class for case database connection objects.
10864 	 */
10865 	abstract class CaseDbConnection implements AutoCloseable {
10866 
10867 		static final int SLEEP_LENGTH_IN_MILLISECONDS = 5000;
10868 		static final int MAX_RETRIES = 20; //MAX_RETRIES * SLEEP_LENGTH_IN_MILLESECONDS = max time to hang attempting connection
10869 
10870 		private class CreateStatement implements DbCommand {
10871 
10872 			private final Connection connection;
10873 			private Statement statement = null;
10874 
10875 			CreateStatement(Connection connection) {
10876 				this.connection = connection;
10877 			}
10878 
10879 			Statement getStatement() {
10880 				return statement;
10881 			}
10882 
10883 			@Override
10884 			public void execute() throws SQLException {
10885 				statement = connection.createStatement();
10886 			}
10887 		}
10888 
10889 		private class SetAutoCommit implements DbCommand {
10890 
10891 			private final Connection connection;
10892 			private final boolean mode;
10893 
10894 			SetAutoCommit(Connection connection, boolean mode) {
10895 				this.connection = connection;
10896 				this.mode = mode;
10897 			}
10898 
10899 			@Override
10900 			public void execute() throws SQLException {
10901 				connection.setAutoCommit(mode);
10902 			}
10903 		}
10904 
10905 		private class Commit implements DbCommand {
10906 
10907 			private final Connection connection;
10908 
10909 			Commit(Connection connection) {
10910 				this.connection = connection;
10911 			}
10912 
10913 			@Override
10914 			public void execute() throws SQLException {
10915 				connection.commit();
10916 			}
10917 		}
10918 
10919 		private class ExecuteQuery implements DbCommand {
10920 
10921 			private final Statement statement;
10922 			private final String query;
10923 			private ResultSet resultSet;
10924 
10925 			ExecuteQuery(Statement statement, String query) {
10926 				this.statement = statement;
10927 				this.query = query;
10928 			}
10929 
10930 			ResultSet getResultSet() {
10931 				return resultSet;
10932 			}
10933 
10934 			@Override
10935 			public void execute() throws SQLException {
10936 				resultSet = statement.executeQuery(query);
10937 			}
10938 		}
10939 
10940 		private class ExecutePreparedStatementQuery implements DbCommand {
10941 
10942 			private final PreparedStatement preparedStatement;
10943 			private ResultSet resultSet;
10944 
10945 			ExecutePreparedStatementQuery(PreparedStatement preparedStatement) {
10946 				this.preparedStatement = preparedStatement;
10947 			}
10948 
10949 			ResultSet getResultSet() {
10950 				return resultSet;
10951 			}
10952 
10953 			@Override
10954 			public void execute() throws SQLException {
10955 				resultSet = preparedStatement.executeQuery();
10956 			}
10957 		}
10958 
10959 		private class ExecutePreparedStatementUpdate implements DbCommand {
10960 
10961 			private final PreparedStatement preparedStatement;
10962 
10963 			ExecutePreparedStatementUpdate(PreparedStatement preparedStatement) {
10964 				this.preparedStatement = preparedStatement;
10965 			}
10966 
10967 			@Override
10968 			public void execute() throws SQLException {
10969 				preparedStatement.executeUpdate();
10970 			}
10971 		}
10972 
10973 		private class ExecuteStatementUpdate implements DbCommand {
10974 
10975 			private final Statement statement;
10976 			private final String updateCommand;
10977 
10978 			ExecuteStatementUpdate(Statement statement, String updateCommand) {
10979 				this.statement = statement;
10980 				this.updateCommand = updateCommand;
10981 			}
10982 
10983 			@Override
10984 			public void execute() throws SQLException {
10985 				statement.executeUpdate(updateCommand);
10986 			}
10987 		}
10988 
10989 		private class ExecuteStatementUpdateGenerateKeys implements DbCommand {
10990 
10991 			private final Statement statement;
10992 			private final int generateKeys;
10993 			private final String updateCommand;
10994 
10995 			ExecuteStatementUpdateGenerateKeys(Statement statement, String updateCommand, int generateKeys) {
10996 				this.statement = statement;
10997 				this.generateKeys = generateKeys;
10998 				this.updateCommand = updateCommand;
10999 			}
11000 
11001 			@Override
11002 			public void execute() throws SQLException {
11003 				statement.executeUpdate(updateCommand, generateKeys);
11004 			}
11005 		}
11006 
11007 		private class PrepareStatement implements DbCommand {
11008 
11009 			private final Connection connection;
11010 			private final String input;
11011 			private PreparedStatement preparedStatement = null;
11012 
11013 			PrepareStatement(Connection connection, String input) {
11014 				this.connection = connection;
11015 				this.input = input;
11016 			}
11017 
11018 			PreparedStatement getPreparedStatement() {
11019 				return preparedStatement;
11020 			}
11021 
11022 			@Override
11023 			public void execute() throws SQLException {
11024 				preparedStatement = connection.prepareStatement(input);
11025 			}
11026 		}
11027 
11028 		private class PrepareStatementGenerateKeys implements DbCommand {
11029 
11030 			private final Connection connection;
11031 			private final String input;
11032 			private final int generateKeys;
11033 			private PreparedStatement preparedStatement = null;
11034 
11035 			PrepareStatementGenerateKeys(Connection connection, String input, int generateKeysInput) {
11036 				this.connection = connection;
11037 				this.input = input;
11038 				this.generateKeys = generateKeysInput;
11039 			}
11040 
11041 			PreparedStatement getPreparedStatement() {
11042 				return preparedStatement;
11043 			}
11044 
11045 			@Override
11046 			public void execute() throws SQLException {
11047 				preparedStatement = connection.prepareStatement(input, generateKeys);
11048 			}
11049 		}
11050 
11051 		abstract void executeCommand(DbCommand command) throws SQLException;
11052 
11053 		private final Connection connection;
11054 		private final Map<PREPARED_STATEMENT, PreparedStatement> preparedStatements;
11055 
11056 		CaseDbConnection(Connection connection) {
11057 			this.connection = connection;
11058 			preparedStatements = new EnumMap<PREPARED_STATEMENT, PreparedStatement>(PREPARED_STATEMENT.class);
11059 		}
11060 
11061 		boolean isOpen() {
11062 			return this.connection != null;
11063 		}
11064 
11065 		PreparedStatement getPreparedStatement(PREPARED_STATEMENT statementKey) throws SQLException {
11066 			return getPreparedStatement(statementKey, Statement.NO_GENERATED_KEYS);
11067 		}
11068 
11069 		PreparedStatement getPreparedStatement(PREPARED_STATEMENT statementKey, int generateKeys) throws SQLException {
11070 			// Lazy statement preparation.
11071 			PreparedStatement statement;
11072 			if (this.preparedStatements.containsKey(statementKey)) {
11073 				statement = this.preparedStatements.get(statementKey);
11074 			} else {
11075 				statement = prepareStatement(statementKey.getSQL(), generateKeys);
11076 				this.preparedStatements.put(statementKey, statement);
11077 			}
11078 			return statement;
11079 		}
11080 
11081 		PreparedStatement prepareStatement(String sqlStatement, int generateKeys) throws SQLException {
11082 			PrepareStatement prepareStatement = new PrepareStatement(this.getConnection(), sqlStatement);
11083 			executeCommand(prepareStatement);
11084 			return prepareStatement.getPreparedStatement();
11085 		}
11086 
11087 		Statement createStatement() throws SQLException {
11088 			CreateStatement createStatement = new CreateStatement(this.connection);
11089 			executeCommand(createStatement);
11090 			return createStatement.getStatement();
11091 		}
11092 
11093 		void beginTransaction() throws SQLException {
11094 			SetAutoCommit setAutoCommit = new SetAutoCommit(connection, false);
11095 			executeCommand(setAutoCommit);
11096 		}
11097 
11098 		void commitTransaction() throws SQLException {
11099 			Commit commit = new Commit(connection);
11100 			executeCommand(commit);
11101 			// You must turn auto commit back on when done with the transaction.
11102 			SetAutoCommit setAutoCommit = new SetAutoCommit(connection, true);
11103 			executeCommand(setAutoCommit);
11104 		}
11105 
11106 		/**
11107 		 * A rollback that logs exceptions and does not throw, intended for
11108 		 * "internal" use in SleuthkitCase methods where the exception that
11109 		 * motivated the rollback is the exception to report to the client.
11110 		 */
11111 		void rollbackTransaction() {
11112 			try {
11113 				connection.rollback();
11114 			} catch (SQLException e) {
11115 				logger.log(Level.SEVERE, "Error rolling back transaction", e);
11116 			}
11117 			try {
11118 				connection.setAutoCommit(true);
11119 			} catch (SQLException e) {
11120 				logger.log(Level.SEVERE, "Error restoring auto-commit", e);
11121 			}
11122 		}
11123 
11124 		/**
11125 		 * A rollback that throws, intended for use by the CaseDbTransaction
11126 		 * class where client code is managing the transaction and the client
11127 		 * may wish to know that the rollback failed.
11128 		 *
11129 		 * @throws SQLException
11130 		 */
11131 		void rollbackTransactionWithThrow() throws SQLException {
11132 			try {
11133 				connection.rollback();
11134 			} finally {
11135 				connection.setAutoCommit(true);
11136 			}
11137 		}
11138 
11139 		ResultSet executeQuery(Statement statement, String query) throws SQLException {
11140 			ExecuteQuery queryCommand = new ExecuteQuery(statement, query);
11141 			executeCommand(queryCommand);
11142 			return queryCommand.getResultSet();
11143 		}
11144 
11145 		/**
11146 		 *
11147 		 * @param statement The SQL statement to execute
11148 		 *
11149 		 * @return returns the ResultSet from the execution of the query
11150 		 *
11151 		 * @throws SQLException \ref query_database_page \ref
11152 		 *                      insert_and_update_database_page
11153 		 */
11154 		ResultSet executeQuery(PreparedStatement statement) throws SQLException {
11155 			ExecutePreparedStatementQuery executePreparedStatementQuery = new ExecutePreparedStatementQuery(statement);
11156 			executeCommand(executePreparedStatementQuery);
11157 			return executePreparedStatementQuery.getResultSet();
11158 		}
11159 
11160 		void executeUpdate(Statement statement, String update) throws SQLException {
11161 			executeUpdate(statement, update, Statement.NO_GENERATED_KEYS);
11162 		}
11163 
11164 		void executeUpdate(Statement statement, String update, int generateKeys) throws SQLException {
11165 			ExecuteStatementUpdate executeStatementUpdate = new ExecuteStatementUpdate(statement, update);
11166 			executeCommand(executeStatementUpdate);
11167 		}
11168 
11169 		void executeUpdate(PreparedStatement statement) throws SQLException {
11170 			ExecutePreparedStatementUpdate executePreparedStatementUpdate = new ExecutePreparedStatementUpdate(statement);
11171 			executeCommand(executePreparedStatementUpdate);
11172 		}
11173 
11174 		/**
11175 		 * Close the connection to the database.
11176 		 */
11177 		@Override
11178 		public void close() {
11179 			try {
11180 				connection.close();
11181 			} catch (SQLException ex) {
11182 				logger.log(Level.SEVERE, "Unable to close connection to case database", ex);
11183 			}
11184 		}
11185 
11186 		Connection getConnection() {
11187 			return this.connection;
11188 		}
11189 	}
11190 
11191 	/**
11192 	 * A connection to an SQLite case database.
11193 	 */
11194 	private final class SQLiteConnection extends CaseDbConnection {
11195 
11196 		private static final int DATABASE_LOCKED_ERROR = 0; // This should be 6 according to documentation, but it has been observed to be 0.
11197 		private static final int SQLITE_BUSY_ERROR = 5;
11198 
11199 		SQLiteConnection(Connection conn) {
11200 			super(conn);
11201 		}
11202 
11203 		@Override
11204 		void executeCommand(DbCommand command) throws SQLException {
11205 			int retryCounter = 0;
11206 			while (true) {
11207 				try {
11208 					command.execute(); // Perform the operation
11209 					break;
11210 				} catch (SQLException ex) {
11211 					if ((ex.getErrorCode() == SQLITE_BUSY_ERROR || ex.getErrorCode() == DATABASE_LOCKED_ERROR) && retryCounter < MAX_RETRIES) {
11212 						try {
11213 
11214 							// We do not notify of error here, as this is not an
11215 							// error condition. It is likely a temporary busy or
11216 							// locked issue and we will retry.
11217 							retryCounter++;
11218 							Thread.sleep(SLEEP_LENGTH_IN_MILLISECONDS);
11219 						} catch (InterruptedException exp) {
11220 							Logger.getLogger(SleuthkitCase.class.getName()).log(Level.WARNING, "Unexpectedly unable to wait for database.", exp);
11221 						}
11222 					} else {
11223 						throw ex;
11224 					}
11225 				}
11226 			}
11227 		}
11228 	}
11229 
11230 	/**
11231 	 * A connection to a PostgreSQL case database.
11232 	 */
11233 	private final class PostgreSQLConnection extends CaseDbConnection {
11234 
11235 		private final String COMMUNICATION_ERROR = PSQLState.COMMUNICATION_ERROR.getState();
11236 		private final String SYSTEM_ERROR = PSQLState.SYSTEM_ERROR.getState();
11237 		private final String UNKNOWN_STATE = PSQLState.UNKNOWN_STATE.getState();
11238 		private static final int MAX_RETRIES = 3;
11239 
11240 		PostgreSQLConnection(Connection conn) {
11241 			super(conn);
11242 		}
11243 
11244 		@Override
11245 		void executeUpdate(Statement statement, String update, int generateKeys) throws SQLException {
11246 			CaseDbConnection.ExecuteStatementUpdateGenerateKeys executeStatementUpdateGenerateKeys = new CaseDbConnection.ExecuteStatementUpdateGenerateKeys(statement, update, generateKeys);
11247 			executeCommand(executeStatementUpdateGenerateKeys);
11248 		}
11249 
11250 		@Override
11251 		PreparedStatement prepareStatement(String sqlStatement, int generateKeys) throws SQLException {
11252 			CaseDbConnection.PrepareStatementGenerateKeys prepareStatementGenerateKeys = new CaseDbConnection.PrepareStatementGenerateKeys(this.getConnection(), sqlStatement, generateKeys);
11253 			executeCommand(prepareStatementGenerateKeys);
11254 			return prepareStatementGenerateKeys.getPreparedStatement();
11255 		}
11256 
11257 		@Override
11258 		void executeCommand(DbCommand command) throws SQLException {
11259 			SQLException lastException = null;
11260 			for (int retries = 0; retries < MAX_RETRIES; retries++) {
11261 				try {
11262 					command.execute();
11263 					lastException = null; // reset since we had a successful execution
11264 					break;
11265 				} catch (SQLException ex) {
11266 					lastException = ex;
11267 					String sqlState = ex.getSQLState();
11268 					if (sqlState == null || sqlState.equals(COMMUNICATION_ERROR) || sqlState.equals(SYSTEM_ERROR) || sqlState.equals(UNKNOWN_STATE)) {
11269 						try {
11270 							Thread.sleep(SLEEP_LENGTH_IN_MILLISECONDS);
11271 						} catch (InterruptedException exp) {
11272 							Logger.getLogger(SleuthkitCase.class.getName()).log(Level.WARNING, "Unexpectedly unable to wait for database.", exp);
11273 						}
11274 					} else {
11275 						throw ex;
11276 					}
11277 				}
11278 			}
11279 
11280 			// rethrow the exception if we bailed because of too many retries
11281 			if (lastException != null) {
11282 				throw lastException;
11283 			}
11284 		}
11285 	}
11286 
11287 	/**
11288 	 * Wraps the transactional capabilities of a CaseDbConnection object to
11289 	 * support use cases where control of a transaction is given to a
11290 	 * SleuthkitCase client. Note that this class does not implement the
11291 	 * Transaction interface because that sort of flexibility and its associated
11292 	 * complexity is not needed. Also, TskCoreExceptions are thrown to be
11293 	 * consistent with the outer SleuthkitCase class.
11294 	 */
11295 	public static final class CaseDbTransaction {
11296 
11297 		private final CaseDbConnection connection;
11298 		private boolean hasWriteLock = false;
11299 		private SleuthkitCase sleuthkitCase;
11300 
11301 		private CaseDbTransaction(SleuthkitCase sleuthkitCase, CaseDbConnection connection) throws TskCoreException {
11302 			this.connection = connection;
11303 			this.sleuthkitCase = sleuthkitCase;
11304 			try {
11305 				this.connection.beginTransaction();
11306 			} catch (SQLException ex) {
11307 				throw new TskCoreException("Failed to create transaction on case database", ex);
11308 			}
11309 		}
11310 
11311 		/**
11312 		 * The implementations of the public APIs that take a CaseDbTransaction
11313 		 * object need access to the underlying CaseDbConnection.
11314 		 *
11315 		 * @return The CaseDbConnection instance for this instance of
11316 		 *         CaseDbTransaction.
11317 		 */
11318 		CaseDbConnection getConnection() {
11319 			return this.connection;
11320 		}
11321 
11322 		/**
11323 		 * Obtain a write lock for this transaction. Only one will be obtained
11324 		 * (no matter how many times it is called) and will be released when
11325 		 * commit or rollback is called.
11326 		 *
11327 		 * If this is not used, you risk deadlock because this transaction can
11328 		 * lock up SQLite and make it "busy" and another thread may get a write
11329 		 * lock to the DB, but not be able to do anything because the DB is
11330 		 * busy.
11331 		 */
11332 		void acquireSingleUserCaseWriteLock() {
11333 			if (!hasWriteLock) {
11334 				hasWriteLock = true;
11335 				sleuthkitCase.acquireSingleUserCaseWriteLock();
11336 			}
11337 		}
11338 
11339 		/**
11340 		 * Commits the transaction on the case database that was begun when this
11341 		 * object was constructed.
11342 		 *
11343 		 * @throws TskCoreException
11344 		 */
11345 		public void commit() throws TskCoreException {
11346 			try {
11347 				this.connection.commitTransaction();
11348 			} catch (SQLException ex) {
11349 				throw new TskCoreException("Failed to commit transaction on case database", ex);
11350 			} finally {
11351 				close();
11352 			}
11353 		}
11354 
11355 		/**
11356 		 * Rolls back the transaction on the case database that was begun when
11357 		 * this object was constructed.
11358 		 *
11359 		 * @throws TskCoreException
11360 		 */
11361 		public void rollback() throws TskCoreException {
11362 			try {
11363 				this.connection.rollbackTransactionWithThrow();
11364 			} catch (SQLException ex) {
11365 				throw new TskCoreException("Case database transaction rollback failed", ex);
11366 			} finally {
11367 				close();
11368 			}
11369 		}
11370 
11371 		/**
11372 		 * Close the database connection
11373 		 *
11374 		 */
11375 		void close() {
11376 			this.connection.close();
11377 			if (hasWriteLock) {
11378 				sleuthkitCase.releaseSingleUserCaseWriteLock();
11379 				hasWriteLock = false;
11380 			}
11381 		}
11382 	}
11383 
11384 	/**
11385 	 * The CaseDbQuery supports the use case where developers have a need for
11386 	 * data that is not exposed through the SleuthkitCase API. A CaseDbQuery
11387 	 * instance gets created through the SleuthkitCase executeDbQuery() method.
11388 	 * It wraps the ResultSet and takes care of acquiring and releasing the
11389 	 * appropriate database lock. It implements AutoCloseable so that it can be
11390 	 * used in a try-with -resources block freeing developers from having to
11391 	 * remember to close the result set and releasing the lock.
11392 	 */
11393 	public final class CaseDbQuery implements AutoCloseable {
11394 
11395 		private ResultSet resultSet;
11396 		private CaseDbConnection connection;
11397 
11398 		private CaseDbQuery(String query) throws TskCoreException {
11399 			this(query, false);
11400 		}
11401 
11402 		private CaseDbQuery(String query, boolean allowWriteQuery) throws TskCoreException {
11403 			if (!allowWriteQuery) {
11404 				if (!query.regionMatches(true, 0, "SELECT", 0, "SELECT".length())) {
11405 					throw new TskCoreException("Unsupported query: Only SELECT queries are supported.");
11406 				}
11407 			}
11408 			try {
11409 				connection = connections.getConnection();
11410 			} catch (TskCoreException ex) {
11411 				throw new TskCoreException("Error getting connection for query: ", ex);
11412 			}
11413 
11414 			try {
11415 				SleuthkitCase.this.acquireSingleUserCaseReadLock();
11416 				resultSet = connection.executeQuery(connection.createStatement(), query);
11417 			} catch (SQLException ex) {
11418 				SleuthkitCase.this.releaseSingleUserCaseReadLock();
11419 				throw new TskCoreException("Error executing query: ", ex);
11420 			}
11421 		}
11422 
11423 		/**
11424 		 * Get the result set for this query.
11425 		 *
11426 		 * @return The result set.
11427 		 */
11428 		public ResultSet getResultSet() {
11429 			return resultSet;
11430 		}
11431 
11432 		@Override
11433 		public void close() throws TskCoreException {
11434 			try {
11435 				if (resultSet != null) {
11436 					final Statement statement = resultSet.getStatement();
11437 					if (statement != null) {
11438 						statement.close();
11439 					}
11440 					resultSet.close();
11441 				}
11442 				connection.close();
11443 			} catch (SQLException ex) {
11444 				throw new TskCoreException("Error closing query: ", ex);
11445 			} finally {
11446 				SleuthkitCase.this.releaseSingleUserCaseReadLock();
11447 			}
11448 		}
11449 	}
11450 
11451 	/**
11452 	 * Add an observer for SleuthkitCase errors.
11453 	 *
11454 	 * @param observer The observer to add.
11455 	 *
11456 	 * @deprecated Catch exceptions instead.
11457 	 */
11458 	@Deprecated
11459 	public void addErrorObserver(ErrorObserver observer) {
11460 		sleuthkitCaseErrorObservers.add(observer);
11461 	}
11462 
11463 	/**
11464 	 * Remove an observer for SleuthkitCase errors.
11465 	 *
11466 	 * @param observer The observer to remove.
11467 	 *
11468 	 * @deprecated Catch exceptions instead.
11469 	 */
11470 	@Deprecated
11471 	public void removeErrorObserver(ErrorObserver observer) {
11472 		int i = sleuthkitCaseErrorObservers.indexOf(observer);
11473 		if (i >= 0) {
11474 			sleuthkitCaseErrorObservers.remove(i);
11475 		}
11476 	}
11477 
11478 	/**
11479 	 * Submit an error to all clients that are listening.
11480 	 *
11481 	 * @param context      The context in which the error occurred.
11482 	 * @param errorMessage A description of the error that occurred.
11483 	 *
11484 	 * @deprecated Catch exceptions instead.
11485 	 */
11486 	@Deprecated
11487 	public void submitError(String context, String errorMessage) {
11488 		for (ErrorObserver observer : sleuthkitCaseErrorObservers) {
11489 			if (observer != null) {
11490 				try {
11491 					observer.receiveError(context, errorMessage);
11492 				} catch (Exception ex) {
11493 					logger.log(Level.SEVERE, "Observer client unable to receive message: {0}, {1}", new Object[]{context, errorMessage, ex});
11494 
11495 				}
11496 			}
11497 		}
11498 	}
11499 
11500 	/**
11501 	 * Notifies observers of errors in the SleuthkitCase.
11502 	 *
11503 	 * @deprecated Catch exceptions instead.
11504 	 */
11505 	@Deprecated
11506 	public interface ErrorObserver {
11507 
11508 		/**
11509 		 * List of arguments for the context string parameters. This does not
11510 		 * preclude the use of arbitrary context strings by client code, but it
11511 		 * does provide a place to define standard context strings to allow
11512 		 * filtering of notifications by implementations of ErrorObserver.
11513 		 */
11514 		public enum Context {
11515 
11516 			/**
11517 			 * Error occurred while reading image content.
11518 			 */
11519 			IMAGE_READ_ERROR("Image File Read Error"),
11520 			/**
11521 			 * Error occurred while reading database content.
11522 			 */
11523 			DATABASE_READ_ERROR("Database Read Error");
11524 
11525 			private final String contextString;
11526 
11527 			private Context(String context) {
11528 				this.contextString = context;
11529 			}
11530 
11531 			public String getContextString() {
11532 				return contextString;
11533 			}
11534 		};
11535 
11536 		void receiveError(String context, String errorMessage);
11537 	}
11538 
11539 	/**
11540 	 * Given an object id, works up the tree of ancestors to the data source for
11541 	 * the object and gets the object id of the data source. The trivial case
11542 	 * where the input object id is for a source is handled.
11543 	 *
11544 	 * @param objectId An object id.
11545 	 *
11546 	 * @return A data source object id.
11547 	 *
11548 	 */
11549 	@Deprecated
11550 	long getDataSourceObjectId(long objectId) {
11551 		try {
11552 			CaseDbConnection connection = connections.getConnection();
11553 			try {
11554 				return getDataSourceObjectId(connection, objectId);
11555 			} finally {
11556 				connection.close();
11557 			}
11558 		} catch (TskCoreException ex) {
11559 			logger.log(Level.SEVERE, "Error getting data source object id for a file", ex);
11560 			return 0;
11561 		}
11562 	}
11563 
11564 	/**
11565 	 * Get last (max) object id of content object in tsk_objects.
11566 	 *
11567 	 * @return currently max id
11568 	 *
11569 	 * @throws TskCoreException exception thrown when database error occurs and
11570 	 *                          last object id could not be queried
11571 	 * @deprecated Do not use, assumes a single-threaded, single-user case.
11572 	 */
11573 	@Deprecated
11574 	public long getLastObjectId() throws TskCoreException {
11575 		CaseDbConnection connection = connections.getConnection();
11576 		acquireSingleUserCaseReadLock();
11577 		ResultSet rs = null;
11578 		try {
11579 			// SELECT MAX(obj_id) AS max_obj_id FROM tsk_objects
11580 			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.SELECT_MAX_OBJECT_ID);
11581 			rs = connection.executeQuery(statement);
11582 			long id = -1;
11583 			if (rs.next()) {
11584 				id = rs.getLong("max_obj_id");
11585 			}
11586 			return id;
11587 		} catch (SQLException e) {
11588 			throw new TskCoreException("Error getting last object id", e);
11589 		} finally {
11590 			closeResultSet(rs);
11591 			connection.close();
11592 			releaseSingleUserCaseReadLock();
11593 		}
11594 	}
11595 
11596 	/**
11597 	 * Find and return list of files matching the specific Where clause. Use
11598 	 * findAllFilesWhere instead. It returns a more generic data type
11599 	 *
11600 	 * @param sqlWhereClause a SQL where clause appropriate for the desired
11601 	 *                       files (do not begin the WHERE clause with the word
11602 	 *                       WHERE!)
11603 	 *
11604 	 * @return a list of FsContent each of which satisfy the given WHERE clause
11605 	 *
11606 	 * @throws TskCoreException
11607 	 * @deprecated	use SleuthkitCase.findAllFilesWhere() instead
11608 	 */
11609 	@Deprecated
11610 	public List<FsContent> findFilesWhere(String sqlWhereClause) throws TskCoreException {
11611 		CaseDbConnection connection = connections.getConnection();
11612 		acquireSingleUserCaseReadLock();
11613 		Statement s = null;
11614 		ResultSet rs = null;
11615 		try {
11616 			s = connection.createStatement();
11617 			rs = connection.executeQuery(s, "SELECT * FROM tsk_files WHERE " + sqlWhereClause); //NON-NLS
11618 			List<FsContent> results = new ArrayList<FsContent>();
11619 			List<AbstractFile> temp = resultSetToAbstractFiles(rs, connection);
11620 			for (AbstractFile f : temp) {
11621 				final TSK_DB_FILES_TYPE_ENUM type = f.getType();
11622 				if (type.equals(TskData.TSK_DB_FILES_TYPE_ENUM.FS)) {
11623 					results.add((FsContent) f);
11624 				}
11625 			}
11626 			return results;
11627 		} catch (SQLException e) {
11628 			throw new TskCoreException("SQLException thrown when calling 'SleuthkitCase.findFilesWhere().", e);
11629 		} finally {
11630 			closeResultSet(rs);
11631 			closeStatement(s);
11632 			connection.close();
11633 			releaseSingleUserCaseReadLock();
11634 		}
11635 	}
11636 
11637 	/**
11638 	 * Get the artifact type id associated with an artifact type name.
11639 	 *
11640 	 * @param artifactTypeName An artifact type name.
11641 	 *
11642 	 * @return An artifact id or -1 if the attribute type does not exist.
11643 	 *
11644 	 * @throws TskCoreException If an error occurs accessing the case database.
11645 	 *
11646 	 * @deprecated Use getArtifactType instead
11647 	 */
11648 	@Deprecated
11649 	public int getArtifactTypeID(String artifactTypeName) throws TskCoreException {
11650 		CaseDbConnection connection = connections.getConnection();
11651 		acquireSingleUserCaseReadLock();
11652 		Statement s = null;
11653 		ResultSet rs = null;
11654 		try {
11655 			s = connection.createStatement();
11656 			rs = connection.executeQuery(s, "SELECT artifact_type_id FROM blackboard_artifact_types WHERE type_name = '" + artifactTypeName + "'"); //NON-NLS
11657 			int typeId = -1;
11658 			if (rs.next()) {
11659 				typeId = rs.getInt("artifact_type_id");
11660 			}
11661 			return typeId;
11662 		} catch (SQLException ex) {
11663 			throw new TskCoreException("Error getting artifact type id", ex);
11664 		} finally {
11665 			closeResultSet(rs);
11666 			closeStatement(s);
11667 			connection.close();
11668 			releaseSingleUserCaseReadLock();
11669 		}
11670 	}
11671 
11672 	/**
11673 	 * Gets a list of the standard blackboard artifact type enum objects.
11674 	 *
11675 	 * @return The members of the BlackboardArtifact.ARTIFACT_TYPE enum.
11676 	 *
11677 	 * @throws TskCoreException Specified, but not thrown.
11678 	 * @deprecated For a list of standard blackboard artifacts type enum
11679 	 * objects, use BlackboardArtifact.ARTIFACT_TYPE.values.
11680 	 */
11681 	@Deprecated
11682 	public ArrayList<BlackboardArtifact.ARTIFACT_TYPE> getBlackboardArtifactTypes() throws TskCoreException {
11683 		return new ArrayList<BlackboardArtifact.ARTIFACT_TYPE>(Arrays.asList(BlackboardArtifact.ARTIFACT_TYPE.values()));
11684 	}
11685 
11686 	/**
11687 	 * Adds a custom artifact type. The artifact type name must be unique, but
11688 	 * the display name need not be unique.
11689 	 *
11690 	 * @param artifactTypeName The artifact type name.
11691 	 * @param displayName      The artifact type display name.
11692 	 *
11693 	 * @return The artifact type id assigned to the artifact type.
11694 	 *
11695 	 * @throws TskCoreException If there is an error adding the type to the case
11696 	 *                          database.
11697 	 * @deprecated Use SleuthkitCase.addBlackboardArtifactType instead.
11698 	 */
11699 	@Deprecated
11700 	public int addArtifactType(String artifactTypeName, String displayName) throws TskCoreException {
11701 		try {
11702 			return addBlackboardArtifactType(artifactTypeName, displayName).getTypeID();
11703 		} catch (TskDataException ex) {
11704 			throw new TskCoreException("Failed to add artifact type.", ex);
11705 		}
11706 	}
11707 
11708 	/**
11709 	 * Adds a custom attribute type with a string value type. The attribute type
11710 	 * name must be unique, but the display name need not be unique.
11711 	 *
11712 	 * @param attrTypeString The attribute type name.
11713 	 * @param displayName    The attribute type display name.
11714 	 *
11715 	 * @return The attribute type id.
11716 	 *
11717 	 * @throws TskCoreException If there is an error adding the type to the case
11718 	 *                          database.
11719 	 * @deprecated Use SleuthkitCase.addArtifactAttributeType instead.
11720 	 */
11721 	@Deprecated
11722 	public int addAttrType(String attrTypeString, String displayName) throws TskCoreException {
11723 		try {
11724 			return addArtifactAttributeType(attrTypeString, TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, displayName).getTypeID();
11725 		} catch (TskDataException ex) {
11726 			throw new TskCoreException("Couldn't add new attribute type");
11727 		}
11728 	}
11729 
11730 	/**
11731 	 * Gets the attribute type id associated with an attribute type name.
11732 	 *
11733 	 * @param attrTypeName An attribute type name.
11734 	 *
11735 	 * @return An attribute id or -1 if the attribute type does not exist.
11736 	 *
11737 	 * @throws TskCoreException If an error occurs accessing the case database.
11738 	 * @deprecated Use SleuthkitCase.getAttributeType instead.
11739 	 */
11740 	@Deprecated
11741 	public int getAttrTypeID(String attrTypeName) throws TskCoreException {
11742 		CaseDbConnection connection = connections.getConnection();
11743 		acquireSingleUserCaseReadLock();
11744 		Statement s = null;
11745 		ResultSet rs = null;
11746 		try {
11747 			s = connection.createStatement();
11748 			rs = connection.executeQuery(s, "SELECT attribute_type_id FROM blackboard_attribute_types WHERE type_name = '" + attrTypeName + "'"); //NON-NLS
11749 			int typeId = -1;
11750 			if (rs.next()) {
11751 				typeId = rs.getInt("attribute_type_id");
11752 			}
11753 			return typeId;
11754 		} catch (SQLException ex) {
11755 			throw new TskCoreException("Error getting attribute type id", ex);
11756 		} finally {
11757 			closeResultSet(rs);
11758 			closeStatement(s);
11759 			connection.close();
11760 			releaseSingleUserCaseReadLock();
11761 		}
11762 	}
11763 
11764 	/**
11765 	 * Get the string associated with the given id. Will throw an error if that
11766 	 * id does not exist
11767 	 *
11768 	 * @param attrTypeID attribute id
11769 	 *
11770 	 * @return string associated with the given id
11771 	 *
11772 	 * @throws TskCoreException exception thrown if a critical error occurs
11773 	 *                          within tsk core
11774 	 * @deprecated Use getAttributeType instead
11775 	 */
11776 	@Deprecated
11777 	public String getAttrTypeString(int attrTypeID) throws TskCoreException {
11778 		CaseDbConnection connection = connections.getConnection();
11779 		acquireSingleUserCaseReadLock();
11780 		Statement s = null;
11781 		ResultSet rs = null;
11782 		try {
11783 			s = connection.createStatement();
11784 			rs = connection.executeQuery(s, "SELECT type_name FROM blackboard_attribute_types WHERE attribute_type_id = " + attrTypeID); //NON-NLS
11785 			if (rs.next()) {
11786 				return rs.getString("type_name");
11787 			} else {
11788 				throw new TskCoreException("No type with that id");
11789 			}
11790 		} catch (SQLException ex) {
11791 			throw new TskCoreException("Error getting or creating a attribute type name", ex);
11792 		} finally {
11793 			closeResultSet(rs);
11794 			closeStatement(s);
11795 			connection.close();
11796 			releaseSingleUserCaseReadLock();
11797 		}
11798 	}
11799 
11800 	/**
11801 	 * Get the display name for the attribute with the given id. Will throw an
11802 	 * error if that id does not exist
11803 	 *
11804 	 * @param attrTypeID attribute id
11805 	 *
11806 	 * @return string associated with the given id
11807 	 *
11808 	 * @throws TskCoreException exception thrown if a critical error occurs
11809 	 *                          within tsk core
11810 	 * @deprecated Use getAttributeType instead
11811 	 */
11812 	@Deprecated
11813 	public String getAttrTypeDisplayName(int attrTypeID) throws TskCoreException {
11814 		CaseDbConnection connection = connections.getConnection();
11815 		acquireSingleUserCaseReadLock();
11816 		Statement s = null;
11817 		ResultSet rs = null;
11818 		try {
11819 			s = connection.createStatement();
11820 			rs = connection.executeQuery(s, "SELECT display_name FROM blackboard_attribute_types WHERE attribute_type_id = " + attrTypeID); //NON-NLS
11821 			if (rs.next()) {
11822 				return rs.getString("display_name");
11823 			} else {
11824 				throw new TskCoreException("No type with that id");
11825 			}
11826 		} catch (SQLException ex) {
11827 			throw new TskCoreException("Error getting or creating a attribute type name", ex);
11828 		} finally {
11829 			closeResultSet(rs);
11830 			closeStatement(s);
11831 			connection.close();
11832 			releaseSingleUserCaseReadLock();
11833 		}
11834 	}
11835 
11836 	/**
11837 	 * Gets a list of the standard blackboard attribute type enum objects.
11838 	 *
11839 	 * @return The members of the BlackboardAttribute.ATTRIBUTE_TYPE enum.
11840 	 *
11841 	 * @throws TskCoreException Specified, but not thrown.
11842 	 * @deprecated For a list of standard blackboard attribute types enum
11843 	 * objects, use BlackboardAttribute.ATTRIBUTE_TYP.values.
11844 	 */
11845 	@Deprecated
11846 	public ArrayList<BlackboardAttribute.ATTRIBUTE_TYPE> getBlackboardAttributeTypes() throws TskCoreException {
11847 		return new ArrayList<BlackboardAttribute.ATTRIBUTE_TYPE>(Arrays.asList(BlackboardAttribute.ATTRIBUTE_TYPE.values()));
11848 	}
11849 
11850 	/**
11851 	 * Process a read-only query on the tsk database, any table Can be used to
11852 	 * e.g. to find files of a given criteria. resultSetToFsContents() will
11853 	 * convert the files to useful objects. MUST CALL closeRunQuery() when done
11854 	 *
11855 	 * @param query the given string query to run
11856 	 *
11857 	 * @return	the resultSet from running the query. Caller MUST CALL
11858 	 *         closeRunQuery(resultSet) as soon as possible, when done with
11859 	 *         retrieving data from the resultSet
11860 	 *
11861 	 * @throws SQLException if error occurred during the query
11862 	 * @deprecated Do not use runQuery(), use executeQuery() instead. \ref
11863 	 * query_database_page
11864 	 */
11865 	@Deprecated
11866 	public ResultSet runQuery(String query) throws SQLException {
11867 		CaseDbConnection connection;
11868 		try {
11869 			connection = connections.getConnection();
11870 		} catch (TskCoreException ex) {
11871 			throw new SQLException("Error getting connection for ad hoc query", ex);
11872 		}
11873 		acquireSingleUserCaseReadLock();
11874 		try {
11875 			return connection.executeQuery(connection.createStatement(), query);
11876 		} finally {
11877 			//TODO unlock should be done in closeRunQuery()
11878 			//but currently not all code calls closeRunQuery - need to fix this
11879 			connection.close();
11880 			releaseSingleUserCaseReadLock();
11881 		}
11882 	}
11883 
11884 	/**
11885 	 * Closes ResultSet and its Statement previously retrieved from runQuery()
11886 	 *
11887 	 * @param resultSet with its Statement to close
11888 	 *
11889 	 * @throws SQLException of closing the query files failed
11890 	 * @deprecated Do not use runQuery() and closeRunQuery(), use executeQuery()
11891 	 * instead. \ref query_database_page
11892 	 */
11893 	@Deprecated
11894 	public void closeRunQuery(ResultSet resultSet) throws SQLException {
11895 		final Statement statement = resultSet.getStatement();
11896 		resultSet.close();
11897 		if (statement != null) {
11898 			statement.close();
11899 		}
11900 	}
11901 
11902 	/**
11903 	 * Adds a carved file to the VirtualDirectory '$CarvedFiles' in the volume
11904 	 * or image given by systemId. Creates $CarvedFiles virtual directory if it
11905 	 * does not exist already.
11906 	 *
11907 	 * @param carvedFileName the name of the carved file to add
11908 	 * @param carvedFileSize the size of the carved file to add
11909 	 * @param containerId    the ID of the parent volume, file system, or image
11910 	 * @param data           the layout information - a list of offsets that
11911 	 *                       make up this carved file.
11912 	 *
11913 	 * @return A LayoutFile object representing the carved file.
11914 	 *
11915 	 * @throws org.sleuthkit.datamodel.TskCoreException
11916 	 * @deprecated Use addCarvedFile(CarvingResult) instead
11917 	 */
11918 	@Deprecated
11919 	public LayoutFile addCarvedFile(String carvedFileName, long carvedFileSize, long containerId, List<TskFileRange> data) throws TskCoreException {
11920 		CarvingResult.CarvedFile carvedFile = new CarvingResult.CarvedFile(carvedFileName, carvedFileSize, data);
11921 		List<CarvingResult.CarvedFile> files = new ArrayList<CarvingResult.CarvedFile>();
11922 		files.add(carvedFile);
11923 		CarvingResult carvingResult;
11924 		Content parent = getContentById(containerId);
11925 		if (parent instanceof FileSystem
11926 				|| parent instanceof Volume
11927 				|| parent instanceof Image) {
11928 			carvingResult = new CarvingResult(parent, files);
11929 		} else {
11930 			throw new TskCoreException(String.format("Parent (id =%d) is not an file system, volume or image", containerId));
11931 		}
11932 		return addCarvedFiles(carvingResult).get(0);
11933 	}
11934 
11935 	/**
11936 	 * Adds a collection of carved files to the VirtualDirectory '$CarvedFiles'
11937 	 * in the volume or image given by systemId. Creates $CarvedFiles virtual
11938 	 * directory if it does not exist already.
11939 	 *
11940 	 * @param filesToAdd A list of CarvedFileContainer files to add as carved
11941 	 *                   files.
11942 	 *
11943 	 * @return A list of the files added to the database.
11944 	 *
11945 	 * @throws org.sleuthkit.datamodel.TskCoreException
11946 	 * @deprecated Use addCarvedFile(CarvingResult) instead
11947 	 */
11948 	@Deprecated
11949 	public List<LayoutFile> addCarvedFiles(List<CarvedFileContainer> filesToAdd) throws TskCoreException {
11950 		List<CarvingResult.CarvedFile> carvedFiles = new ArrayList<CarvingResult.CarvedFile>();
11951 		for (CarvedFileContainer container : filesToAdd) {
11952 			CarvingResult.CarvedFile carvedFile = new CarvingResult.CarvedFile(container.getName(), container.getSize(), container.getRanges());
11953 			carvedFiles.add(carvedFile);
11954 		}
11955 		CarvingResult carvingResult;
11956 		Content parent = getContentById(filesToAdd.get(0).getId());
11957 		if (parent instanceof FileSystem
11958 				|| parent instanceof Volume
11959 				|| parent instanceof Image) {
11960 			carvingResult = new CarvingResult(parent, carvedFiles);
11961 		} else {
11962 			throw new TskCoreException(String.format("Parent (id =%d) is not an file system, volume or image", parent.getId()));
11963 		}
11964 		return addCarvedFiles(carvingResult);
11965 	}
11966 
11967 	/**
11968 	 * Creates a new derived file object, adds it to database and returns it.
11969 	 *
11970 	 * TODO add support for adding derived method
11971 	 *
11972 	 * @param fileName        file name the derived file
11973 	 * @param localPath       local path of the derived file, including the file
11974 	 *                        name. The path is relative to the database path.
11975 	 * @param size            size of the derived file in bytes
11976 	 * @param ctime           The changed time of the file.
11977 	 * @param crtime          The creation time of the file.
11978 	 * @param atime           The accessed time of the file
11979 	 * @param mtime           The modified time of the file.
11980 	 * @param isFile          whether a file or directory, true if a file
11981 	 * @param parentFile      parent file object (derived or local file)
11982 	 * @param rederiveDetails details needed to re-derive file (will be specific
11983 	 *                        to the derivation method), currently unused
11984 	 * @param toolName        name of derivation method/tool, currently unused
11985 	 * @param toolVersion     version of derivation method/tool, currently
11986 	 *                        unused
11987 	 * @param otherDetails    details of derivation method/tool, currently
11988 	 *                        unused
11989 	 *
11990 	 * @return newly created derived file object
11991 	 *
11992 	 * @throws TskCoreException exception thrown if the object creation failed
11993 	 *                          due to a critical system error
11994 	 * @deprecated Use the newer version with explicit encoding type parameter
11995 	 */
11996 	@Deprecated
11997 	public DerivedFile addDerivedFile(String fileName, String localPath,
11998 			long size, long ctime, long crtime, long atime, long mtime,
11999 			boolean isFile, AbstractFile parentFile,
12000 			String rederiveDetails, String toolName, String toolVersion, String otherDetails) throws TskCoreException {
12001 		return addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime,
12002 				isFile, parentFile, rederiveDetails, toolName, toolVersion,
12003 				otherDetails, TskData.EncodingType.NONE);
12004 	}
12005 
12006 	/**
12007 	 * Adds a local/logical file to the case database. The database operations
12008 	 * are done within a caller-managed transaction; the caller is responsible
12009 	 * for committing or rolling back the transaction.
12010 	 *
12011 	 * @param fileName    The name of the file.
12012 	 * @param localPath   The absolute path (including the file name) of the
12013 	 *                    local/logical in secondary storage.
12014 	 * @param size        The size of the file in bytes.
12015 	 * @param ctime       The changed time of the file.
12016 	 * @param crtime      The creation time of the file.
12017 	 * @param atime       The accessed time of the file
12018 	 * @param mtime       The modified time of the file.
12019 	 * @param isFile      True, unless the file is a directory.
12020 	 * @param parent      The parent of the file (e.g., a virtual directory)
12021 	 * @param transaction A caller-managed transaction within which the add file
12022 	 *                    operations are performed.
12023 	 *
12024 	 * @return An object representing the local/logical file.
12025 	 *
12026 	 * @throws TskCoreException if there is an error completing a case database
12027 	 *                          operation.
12028 	 * @deprecated Use the newer version with explicit encoding type parameter
12029 	 */
12030 	@Deprecated
12031 	public LocalFile addLocalFile(String fileName, String localPath,
12032 			long size, long ctime, long crtime, long atime, long mtime,
12033 			boolean isFile,
12034 			AbstractFile parent, CaseDbTransaction transaction) throws TskCoreException {
12035 		return addLocalFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile,
12036 				TskData.EncodingType.NONE, parent, transaction);
12037 	}
12038 
12039 	/**
12040 	 * Wraps the version of addLocalFile that takes a Transaction in a
12041 	 * transaction local to this method.
12042 	 *
12043 	 * @param fileName
12044 	 * @param localPath
12045 	 * @param size
12046 	 * @param ctime
12047 	 * @param crtime
12048 	 * @param atime
12049 	 * @param mtime
12050 	 * @param isFile
12051 	 * @param parent
12052 	 *
12053 	 * @return
12054 	 *
12055 	 * @throws TskCoreException
12056 	 * @deprecated Use the newer version with explicit encoding type parameter
12057 	 */
12058 	@Deprecated
12059 	public LocalFile addLocalFile(String fileName, String localPath,
12060 			long size, long ctime, long crtime, long atime, long mtime,
12061 			boolean isFile,
12062 			AbstractFile parent) throws TskCoreException {
12063 		return addLocalFile(fileName, localPath, size, ctime, crtime, atime, mtime,
12064 				isFile, TskData.EncodingType.NONE, parent);
12065 	}
12066 
12067 	/**
12068 	 * Start process of adding a image to the case. Adding an image is a
12069 	 * multi-step process and this returns an object that allows it to happen.
12070 	 *
12071 	 * @param timezone        TZ time zone string to use for ingest of image.
12072 	 * @param addUnallocSpace Set to true to create virtual files for
12073 	 *                        unallocated space in the image.
12074 	 * @param noFatFsOrphans  Set to true to skip processing orphan files of FAT
12075 	 *                        file systems.
12076 	 *
12077 	 * @return Object that encapsulates control of adding an image via the
12078 	 *         SleuthKit native code layer
12079 	 *
12080 	 * @deprecated Use the newer version with explicit image writer path
12081 	 * parameter
12082 	 */
12083 	@Deprecated
12084 	public AddImageProcess makeAddImageProcess(String timezone, boolean addUnallocSpace, boolean noFatFsOrphans) {
12085 		return this.caseHandle.initAddImageProcess(timezone, addUnallocSpace, noFatFsOrphans, "", this);
12086 	}
12087 
12088 	/**
12089 	 * Acquires a write lock, but only if this is a single-user case. Always
12090 	 * call this method in a try block with a call to the lock release method in
12091 	 * an associated finally block.
12092 	 *
12093 	 * @deprecated Use acquireSingleUserCaseWriteLock.
12094 	 */
12095 	@Deprecated
12096 	public void acquireExclusiveLock() {
12097 		acquireSingleUserCaseWriteLock();
12098 	}
12099 
12100 	/**
12101 	 * Releases a write lock, but only if this is a single-user case. This
12102 	 * method should always be called in the finally block of a try block in
12103 	 * which the lock was acquired.
12104 	 *
12105 	 * @deprecated Use releaseSingleUserCaseWriteLock.
12106 	 */
12107 	@Deprecated
12108 	public void releaseExclusiveLock() {
12109 		releaseSingleUserCaseWriteLock();
12110 	}
12111 
12112 	/**
12113 	 * Acquires a read lock, but only if this is a single-user case. Call this
12114 	 * method in a try block with a call to the lock release method in an
12115 	 * associated finally block.
12116 	 *
12117 	 * @deprecated Use acquireSingleUserCaseReadLock.
12118 	 */
12119 	@Deprecated
12120 	public void acquireSharedLock() {
12121 		acquireSingleUserCaseReadLock();
12122 	}
12123 
12124 	/**
12125 	 * Releases a read lock, but only if this is a single-user case. This method
12126 	 * should always be called in the finally block of a try block in which the
12127 	 * lock was acquired.
12128 	 *
12129 	 * @deprecated Use releaseSingleUserCaseReadLock.
12130 	 */
12131 	@Deprecated
12132 	public void releaseSharedLock() {
12133 		releaseSingleUserCaseReadLock();
12134 	}
12135 };
12136