diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 6d0aca0f6e..299af11ea0 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -219,7 +219,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* multi-user (using PostgreSql)
*/
@NbBundle.Messages({"Case_caseType_singleUser=Single-user case",
- "Case_caseType_multiUser=Multi-user case"})
+ "Case_caseType_multiUser=Multi-user case"})
public enum CaseType {
SINGLE_USER_CASE(Bundle.Case_caseType_singleUser()),
@@ -257,6 +257,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
private String examiner;
private String configFilePath;
private final XMLCaseManagement xmlcm;
+ private final CaseMetadata caseMetadata;
private final SleuthkitCase db;
// Track the current case (only set with changeCase() method)
private static Case currentCase = null;
@@ -280,12 +281,13 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Constructor for the Case class
*/
- private Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db, CaseType type) {
+ private Case(String name, String number, String examiner, String configFilePath, CaseMetadata caseMetadata, SleuthkitCase db, CaseType type) {
this.name = name;
this.number = number;
this.examiner = examiner;
this.configFilePath = configFilePath;
- this.xmlcm = xmlcm;
+ this.xmlcm = new XMLCaseManagement();
+ this.caseMetadata = caseMetadata;
this.caseType = type;
this.db = db;
this.services = new Services(db);
@@ -403,46 +405,47 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Creates a single-user new case.
*
- * @param caseDir The full path of the case directory. It will be created
- * if it doesn't already exist; if it exists, it should
- * have been created using Case.createCaseDirectory() to
- * ensure that the required sub-directories aere created.
- * @param caseName The name of case.
+ * @param caseDir The full path of the case directory. It will be created if
+ * it doesn't already exist; if it exists, it should have been created using
+ * Case.createCaseDirectory() to ensure that the required sub-directories
+ * aere created.
+ * @param caseName The name of case.
* @param caseNumber The case number, can be the empty string.
- * @param examiner The examiner to associate with the case, can be the
- * empty string.
+ * @param examiner The examiner to associate with the case, can be the empty
+ * string.
*
* @throws CaseActionException if there is a problem creating the case. The
- * exception will have a user-friendly message
- * and may be a wrapper for a lower-level
- * exception. If so,
- * CaseActionException.getCause will return a
- * Throwable (null otherwise).
+ * exception will have a user-friendly message and may be a wrapper for a
+ * lower-level exception. If so, CaseActionException.getCause will return a
+ * Throwable (null otherwise).
+ * @throws CaseMetadataException if there is a problem creating the case
+ * metadata.
*/
- public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
+ public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException, CaseMetadataException {
create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
}
/**
* Creates a new case.
*
- * @param caseDir The full path of the case directory. It will be created
- * if it doesn't already exist; if it exists, it should
- * have been created using Case.createCaseDirectory() to
- * ensure that the required sub-directories aere created.
- * @param caseName The name of case.
+ * @param caseDir The full path of the case directory. It will be created if
+ * it doesn't already exist; if it exists, it should have been created using
+ * Case.createCaseDirectory() to ensure that the required sub-directories
+ * aere created.
+ * @param caseName The name of case.
* @param caseNumber The case number, can be the empty string.
- * @param examiner The examiner to associate with the case, can be the
- * empty string.
- * @param caseType The type of case (single-user or multi-user). The
- * exception will have a user-friendly message and may be
- * a wrapper for a lower-level exception. If so,
- * CaseActionException.getCause will return a Throwable
- * (null otherwise).
+ * @param examiner The examiner to associate with the case, can be the empty
+ * string.
+ * @param caseType The type of case (single-user or multi-user). The
+ * exception will have a user-friendly message and may be a wrapper for a
+ * lower-level exception. If so, CaseActionException.getCause will return a
+ * Throwable (null otherwise).
*
* @throws CaseActionException if there is a problem creating the case.
+ * @throws CaseMetadataException if there is a problem creating the case
+ * metadata.
*/
- public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
+ public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseMetadataException {
logger.log(Level.INFO, "Creating case with case directory {0}, caseName {1}", new Object[]{caseDir, caseName}); //NON-NLS
/*
@@ -506,8 +509,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
});
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
}
+ CaseMetadata metadata = new CaseMetadata(caseType, caseName, caseNumber, examiner, caseDir, dbName, indexName);
- Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db, caseType);
+ Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, metadata, db, caseType);
changeCase(newCase);
}
@@ -578,11 +582,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @param caseMetadataFilePath The path of the case metadata file.
*
* @throws CaseActionException if there is a problem opening the case. The
- * exception will have a user-friendly message
- * and may be a wrapper for a lower-level
- * exception. If so,
- * CaseActionException.getCause will return a
- * Throwable (null otherwise).
+ * exception will have a user-friendly message and may be a wrapper for a
+ * lower-level exception. If so, CaseActionException.getCause will return a
+ * Throwable (null otherwise).
*/
public static void open(String caseMetadataFilePath) throws CaseActionException {
logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
@@ -671,9 +673,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* TODO (AUT-1885): Replace use of obsolete and unsafe
* XMLCaseManagement class with use of CaseMetadata class.
*/
- XMLCaseManagement xmlcm = new XMLCaseManagement();
- xmlcm.open(caseMetadataFilePath);
- Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, xmlcm, db, caseType);
+ Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, metadata, db, caseType);
changeCase(openedCase);
} catch (CaseMetadataException ex) {
@@ -710,7 +710,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Sends out event and reopens windows if needed.
*
* @param imgPaths the paths of the image that being added
- * @param imgId the ID of the image that being added
+ * @param imgId the ID of the image that being added
* @param timeZone the timeZone of the image where it's added
*
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
@@ -750,8 +750,8 @@ public class Case implements SleuthkitCase.ErrorObserver {
* This should not be called from the event dispatch thread (EDT)
*
* @param dataSourceId A unique identifier for the data source. This UUID
- * should be used to call notifyNewDataSource() after
- * the data source is added.
+ * should be used to call notifyNewDataSource() after the data source is
+ * added.
*/
public void notifyAddingDataSource(UUID dataSourceId) {
eventPublisher.publish(new AddingDataSourceEvent(dataSourceId));
@@ -776,10 +776,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
* This should not be called from the event dispatch thread (EDT)
*
* @param newDataSource New data source added.
- * @param dataSourceId A unique identifier for the data source. Should be
- * the same UUID used to call
- * notifyAddingNewDataSource() when the process of
- * adding the data source began.
+ * @param dataSourceId A unique identifier for the data source. Should be
+ * the same UUID used to call notifyAddingNewDataSource() when the process
+ * of adding the data source began.
*/
public void notifyDataSourceAdded(Content newDataSource, UUID dataSourceId) {
eventPublisher.publish(new DataSourceAddedEvent(newDataSource, dataSourceId));
@@ -894,9 +893,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
* This should not be called from the EDT.
*
* @param oldCaseName the old case name that wants to be updated
- * @param oldPath the old path that wants to be updated
+ * @param oldPath the old path that wants to be updated
* @param newCaseName the new case name
- * @param newPath the new path
+ * @param newPath the new path
*/
void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
try {
@@ -1317,7 +1316,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Adds a subscriber to events from this Autopsy node and other Autopsy
* nodes.
*
- * @param eventName The event the subscriber is interested in.
+ * @param eventName The event the subscriber is interested in.
* @param subscriber The subscriber to add.
*/
public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
@@ -1328,7 +1327,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Adds a subscriber to events from this Autopsy node and other Autopsy
* nodes.
*
- * @param eventName The event the subscriber is no longer interested in.
+ * @param eventName The event the subscriber is no longer interested in.
* @param subscriber The subscriber to add.
*/
public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
@@ -1438,7 +1437,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* to create the case directory
*
- * @param caseDir Path to the case directory (typically base + case name)
+ * @param caseDir Path to the case directory (typically base + case name)
* @param caseName the case name (used only for error messages)
*
* @throws CaseActionException throw if could not create the case dir
@@ -1452,7 +1451,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Create the case directory and its needed subfolders.
*
- * @param caseDir Path to the case directory (typically base + case name)
+ * @param caseDir Path to the case directory (typically base + case name)
* @param caseType The type of case, single-user or multi-user
*
* @throws CaseActionException throw if could not create the case dir
@@ -1671,10 +1670,10 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Adds a report to the case.
*
- * @param localPath The path of the report file, must be in the case
- * directory or one of its subdirectories.
+ * @param localPath The path of the report file, must be in the case
+ * directory or one of its subdirectories.
* @param srcModuleName The name of the module that created the report.
- * @param reportName The report name, may be empty.
+ * @param reportName The report name, may be empty.
*
* @throws TskCoreException
*/
@@ -1698,9 +1697,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Deletes reports from the case - deletes it from the disk as well as the
* database.
*
- * @param reports Collection of Report to be deleted from the case.
+ * @param reports Collection of Report to be deleted from the case.
* @param deleteFromDisk Set true to perform reports file deletion from
- * disk.
+ * disk.
*
* @throws TskCoreException
*/
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
index 81e53a1fc9..034f7c41de 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -18,7 +18,34 @@
*/
package org.sleuthkit.autopsy.casemodule;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
import java.nio.file.Path;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.Level;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.openide.util.NbBundle;
+import static org.sleuthkit.autopsy.casemodule.XMLCaseManagement.TOP_ROOT_NAME;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
/**
* Provides access to case metadata.
@@ -42,6 +69,37 @@ public final class CaseMetadata {
}
}
+ final static String XSDFILE = "CaseSchema.xsd"; //NON-NLS
+ final static String TOP_ROOT_NAME = "AutopsyCase"; //NON-NLS
+ final static String CASE_ROOT_NAME = "Case"; //NON-NLS
+ // general metadata about the case file
+ final static String NAME = "Name"; //NON-NLS
+ final static String NUMBER = "Number"; //NON-NLS
+ final static String EXAMINER = "Examiner"; //NON-NLS
+ final static String CREATED_DATE_NAME = "CreatedDate"; //NON-NLS
+ final static String MODIFIED_DATE_NAME = "ModifiedDate"; //NON-NLS
+ final static String SCHEMA_VERSION_NAME = "SchemaVersion"; //NON-NLS
+ final static String AUTOPSY_CRVERSION_NAME = "AutopsyCreatedVersion"; //NON-NLS
+ final static String AUTOPSY_MVERSION_NAME = "AutopsySavedVersion"; //NON-NLS
+ final static String CASE_TEXT_INDEX_NAME = "TextIndexName"; //NON-NLS
+ // folders inside case directory
+ final static String LOG_FOLDER_NAME = "LogFolder"; //NON-NLS
+ final static String LOG_FOLDER_RELPATH = "Log"; //NON-NLS
+ final static String TEMP_FOLDER_NAME = "TempFolder"; //NON-NLS
+ final static String TEMP_FOLDER_RELPATH = "Temp"; //NON-NLS
+ final static String EXPORT_FOLDER_NAME = "ExportFolder"; //NON-NLS
+ final static String EXPORT_FOLDER_RELPATH = "Export"; //NON-NLS
+ final static String CACHE_FOLDER_NAME = "CacheFolder"; //NON-NLS
+ final static String CACHE_FOLDER_RELPATH = "Cache"; //NON-NLS
+ final static String CASE_TYPE = "CaseType"; //NON-NLS
+ final static String DATABASE_NAME = "DatabaseName"; //NON-NLS
+ // folders attribute
+ final static String RELATIVE_NAME = "Relative"; // relevant path info NON-NLS
+ // folder attr values
+ final static String RELATIVE_TRUE = "true"; // if it's a relative path NON-NLS
+ final static String RELATIVE_FALSE = "false"; // if it's not a relative path NON-NLS
+
+ private Document doc;
private final Case.CaseType caseType;
private final String caseName;
private final String caseNumber;
@@ -49,6 +107,18 @@ public final class CaseMetadata {
private final String caseDirectory;
private final String caseDatabaseName;
private final String caseTextIndexName;
+ private static final Logger logger = Logger.getLogger(CaseMetadata.class.getName());
+
+ public CaseMetadata(Case.CaseType caseType, String caseName, String caseNumber, String exainer, String caseDirectory, String caseDatabaseName, String caseTextIndexName) throws CaseMetadataException {
+ this.caseType = caseType;
+ this.caseName = caseName;
+ this.caseNumber = caseNumber;
+ this.examiner = exainer;
+ this.caseDirectory = caseDirectory;
+ this.caseDatabaseName = caseDatabaseName;
+ this.caseTextIndexName = caseTextIndexName;
+ this.create();
+ }
/**
* Constructs an object that provides access to case metadata.
@@ -110,6 +180,7 @@ public final class CaseMetadata {
} catch (CaseActionException ex) {
throw new CaseMetadataException(ex.getLocalizedMessage(), ex);
}
+ this.create();
}
/**
@@ -175,4 +246,145 @@ public final class CaseMetadata {
return caseTextIndexName;
}
+ private void create() throws CaseMetadataException {
+ DocumentBuilder docBuilder;
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+
+ // throw an error here
+ try {
+ docBuilder = docFactory.newDocumentBuilder();
+ } catch (ParserConfigurationException ex) {
+ clear();
+ throw new CaseMetadataException(
+ NbBundle.getMessage(this.getClass(), "XMLCaseManagement.create.exception.msg"), ex);
+ }
+ DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
+
+ doc = docBuilder.newDocument();
+ Element rootElement = doc.createElement(TOP_ROOT_NAME); // ...
+ doc.appendChild(rootElement);
+
+ Element crDateElement = doc.createElement(CREATED_DATE_NAME); // ...
+ crDateElement.appendChild(doc.createTextNode(dateFormat.format(new Date())));
+ rootElement.appendChild(crDateElement);
+
+ Element mDateElement = doc.createElement(MODIFIED_DATE_NAME); // ...
+ mDateElement.appendChild(doc.createTextNode(dateFormat.format(new Date())));
+ rootElement.appendChild(mDateElement);
+
+ Element autVerElement = doc.createElement(AUTOPSY_CRVERSION_NAME); // ...
+ autVerElement.appendChild(doc.createTextNode(System.getProperty("netbeans.buildnumber")));
+ rootElement.appendChild(autVerElement);
+
+ Element autSavedVerElement = doc.createElement(AUTOPSY_MVERSION_NAME); // ...
+ autSavedVerElement.appendChild(doc.createTextNode(System.getProperty("netbeans.buildnumber")));
+ rootElement.appendChild(autSavedVerElement);
+
+ Element schVerElement = doc.createElement(SCHEMA_VERSION_NAME); // ...
+ schVerElement.appendChild(doc.createTextNode(schemaVersion));
+ rootElement.appendChild(schVerElement);
+
+ Element caseElement = doc.createElement(CASE_ROOT_NAME); // ...
+ rootElement.appendChild(caseElement);
+
+ Element nameElement = doc.createElement(NAME); // ...
+ nameElement.appendChild(doc.createTextNode(caseName));
+ caseElement.appendChild(nameElement);
+
+ Element numberElement = doc.createElement(NUMBER); // ...
+ numberElement.appendChild(doc.createTextNode(String.valueOf(caseNumber)));
+ caseElement.appendChild(numberElement);
+
+ Element examinerElement = doc.createElement(EXAMINER); // ...
+ examinerElement.appendChild(doc.createTextNode(examiner));
+ caseElement.appendChild(examinerElement);
+
+ Element exportElement = doc.createElement(EXPORT_FOLDER_NAME); // ...
+ exportElement.appendChild(doc.createTextNode(EXPORT_FOLDER_RELPATH));
+ exportElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS
+ caseElement.appendChild(exportElement);
+
+ Element logElement = doc.createElement(LOG_FOLDER_NAME); // ...
+ logElement.appendChild(doc.createTextNode(LOG_FOLDER_RELPATH));
+ logElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS
+ caseElement.appendChild(logElement);
+
+ Element tempElement = doc.createElement(TEMP_FOLDER_NAME); // ...
+ tempElement.appendChild(doc.createTextNode(TEMP_FOLDER_RELPATH));
+ tempElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS
+ caseElement.appendChild(tempElement);
+
+ Element cacheElement = doc.createElement(CACHE_FOLDER_NAME); // ...
+ cacheElement.appendChild(doc.createTextNode(CACHE_FOLDER_RELPATH));
+ cacheElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS
+ caseElement.appendChild(cacheElement);
+
+ Element typeElement = doc.createElement(CASE_TYPE); // ...
+ typeElement.appendChild(doc.createTextNode(caseType.toString()));
+ caseElement.appendChild(typeElement);
+
+ Element dbNameElement = doc.createElement(DATABASE_NAME); // ...
+ dbNameElement.appendChild(doc.createTextNode(this.caseDatabaseName));
+ caseElement.appendChild(dbNameElement);
+
+ Element indexNameElement = doc.createElement(CASE_TEXT_INDEX_NAME); // ...
+ indexNameElement.appendChild(doc.createTextNode(this.caseTextIndexName));
+ caseElement.appendChild(indexNameElement);
+ this.writeFile();
+ }
+
+ private void writeFile() throws CaseMetadataException {
+ if (doc == null || caseName.equals("")) {
+ throw new CaseMetadataException(
+ NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.noCase.msg"));
+ }
+
+ // Prepare the DOM document for writing
+ Source source = new DOMSource(doc);
+
+ // Prepare the data for the output file
+ StringWriter sw = new StringWriter();
+ Result result = new StreamResult(sw);
+
+ // Write the DOM document to the file
+ Transformer xformer;// = TransformerFactory.newInstance().newTransformer();
+ TransformerFactory tfactory = TransformerFactory.newInstance();
+
+ try {
+ xformer = tfactory.newTransformer();
+ } catch (TransformerConfigurationException ex) {
+ logger.log(Level.SEVERE, "Could not setup tranformer and write case file"); //NON-NLS
+ throw new CaseMetadataException(
+ NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex);
+ }
+
+ //Setup indenting to "pretty print"
+ xformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
+ xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
+
+ try {
+ xformer.transform(source, result);
+ } catch (TransformerException ex) {
+ logger.log(Level.SEVERE, "Could not run tranformer and write case file"); //NON-NLS
+ throw new CaseMetadataException(
+ NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex);
+ }
+
+ // preparing the output file
+ String xmlString = sw.toString();
+ File file = new File(this.caseDirectory + File.separator + caseName + ".aut");
+
+ // write the file
+ try {
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
+ bw.write(xmlString);
+ bw.flush();
+ bw.close();
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Error writing to case file"); //NON-NLS
+ throw new CaseMetadataException(
+ NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex);
+ }
+ }
+
}