diff --git a/NEWS.txt b/NEWS.txt
index 2cbd1d7b2f3ef8888a30376d618d9bca41248c3d..6940c728c0fd594815a84d97bf373e1571cf359e 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,17 @@
+---------------- VERSION 4.10.0 --------------
+C/C++:
+- Removed PostgreSQL code (that was used only by Java code)
+- Added Java callback support so that database inserts are done in Java.
+
+Java: 
+- Added methods and callbacks as required to allow database population to happen in Java instead of C/C++.
+- Added support to allow Autopsy streaming ingest where files are added in batches. 
+- Added TaggingManager class and concept of a TagSet to support ProjectVic categories. 
+- Fixed changes to normalization and validation of emails and phone numbers.
+- Added a CASE/UCO JAR file that creates JSON-LD based on TSK objects.
+
+
+
 ---------------- VERSION 4.9.0 --------------
 C/C++
 - Removed framework project.  Use Autopsy instead if you need an analysis framework. 
diff --git a/bindings/java/build.xml b/bindings/java/build.xml
index a5c24c0204924182ac5ab781509dce51849e2a99..5dad71b5f0f0eb49b2b6b5896b5d2e9c1936b3b9 100644
--- a/bindings/java/build.xml
+++ b/bindings/java/build.xml
@@ -11,7 +11,7 @@
 	<import file="build-${os.family}.xml"/>
 
     <!-- Careful changing this because release-windows.pl updates it by pattern -->
-<property name="VERSION" value="4.9.0"/>
+<property name="VERSION" value="4.10.0"/>
 
 	<!-- set global properties for this build -->
 	<property name="default-jar-location" location="/usr/share/java"/>
diff --git a/bindings/java/doxygen/Doxyfile b/bindings/java/doxygen/Doxyfile
index 7c615e85573d81cd944dec2b9eba95450701705b..c89d48702733ea190eb4bcd62f5eddd8bb9a83a0 100644
--- a/bindings/java/doxygen/Doxyfile
+++ b/bindings/java/doxygen/Doxyfile
@@ -39,7 +39,7 @@ PROJECT_NAME           = "Sleuth Kit Java Bindings (JNI)"
 # control system is used.
 
 # NOTE: This is updated by the release-unix.pl script
-PROJECT_NUMBER = 4.9.0
+PROJECT_NUMBER = 4.10.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -1050,7 +1050,7 @@ GENERATE_HTML          = YES
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 # NOTE: This is updated by the release-unix.pl script
-HTML_OUTPUT = jni-docs/4.9.0/
+HTML_OUTPUT = jni-docs/4.10.0/
 
 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
 # generated HTML page (for example: .htm, .php, .asp).
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 2f07f18b0d7e11f86d2435b78e016aa99c384c5f..417369d48d5f4e1a105404b120320b72805f43ce 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -183,6 +183,7 @@ public class SleuthkitCase {
 	private final DbType dbType;
 	private final String caseDirPath;
 	private SleuthkitJNI.CaseDbHandle caseHandle;
+	private final String caseHandleIdentifier; // Used to identify this case in the JNI cache.
 	private String dbBackupPath;
 	private Map<Integer, BlackboardArtifact.Type> typeIdToArtifactTypeMap;
 	private Map<Integer, BlackboardAttribute.Type> typeIdToAttributeTypeMap;
@@ -316,6 +317,7 @@ private SleuthkitCase(String dbPath, SleuthkitJNI.CaseDbHandle caseHandle, DbTyp
 		this.databaseName = dbFile.getName();
 		this.connections = new SQLiteConnections(dbPath);
 		this.caseHandle = caseHandle;
+		this.caseHandleIdentifier = caseHandle.getCaseDbIdentifier();
 		init();
 		logSQLiteJDBCDriverInfo();
 	}
@@ -344,6 +346,7 @@ private SleuthkitCase(String host, int port, String dbName, String userName, Str
 		this.caseDirPath = caseDirPath;
 		this.connections = new PostgreSQLConnections(host, port, dbName, userName, password);
 		this.caseHandle = caseHandle;
+		this.caseHandleIdentifier = caseHandle.getCaseDbIdentifier();
 		init();
 	}
 
@@ -8947,11 +8950,14 @@ CaseDbConnection getConnection() throws TskCoreException {
 		return connections.getConnection();
 	}
 
-	synchronized String getUniqueCaseIdentifier() throws TskCoreException {
-		if (caseHandle != null) {
-			return caseHandle.getCaseDbIdentifier();
-		}
-		throw new TskCoreException("Case has been closed");
+	/**
+	 * Gets the string used to identify this case in the JNI cache.
+	 * 
+	 * @return The string for this case
+	 * @throws TskCoreException 
+	 */
+	String getCaseHandleIdentifier() {
+		return caseHandleIdentifier;
 	}
 
 	@Override
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
index 104488c1e4fd1e6bf7e10b302d2ebcc6fd63099b..745599c240e3ca8bb2f90e04bb7c04bdedabc597 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -251,12 +251,7 @@ private static void removeFileHandle(long fileHandle, SleuthkitCase skCase) {
 			synchronized (cacheLock) {
 				// Remove from collection of open file handles.
 				if (skCase != null) {
-					try {
-						getCaseHandles(skCase.getUniqueCaseIdentifier()).fileHandleCache.remove(fileHandle);
-					} catch (TskCoreException ex) {
-						// This exception will only occur if a file handle is closed as the case is being closed.
-						// The file will be closed by the case closing code.
-					}
+					getCaseHandles(skCase.getCaseHandleIdentifier()).fileHandleCache.remove(fileHandle);
 				} else {
 					// If we don't know what case the handle is from, delete the first one we find
 					for (String caseIdentifier:caseHandlesCache.keySet()) {
@@ -789,7 +784,7 @@ public static long openImage(String[] imageFiles, SleuthkitCase skCase) throws T
 		if (skCase == null) {
 			throw new TskCoreException("SleuthkitCase can not be null");
 		}
-		return openImage(imageFiles, 0, true, skCase.getUniqueCaseIdentifier());
+		return openImage(imageFiles, 0, true, skCase.getCaseHandleIdentifier());
 	}
 
 	/**
@@ -809,7 +804,7 @@ public static long openImage(String[] imageFiles, int sSize, SleuthkitCase skCas
 		if (skCase == null) {
 			throw new TskCoreException("SleuthkitCase can not be null");
 		}
-		return openImage(imageFiles, sSize, true, skCase.getUniqueCaseIdentifier());
+		return openImage(imageFiles, sSize, true, skCase.getCaseHandleIdentifier());
 	}
 	
 	/**
@@ -898,15 +893,11 @@ private static void cacheImageHandle(SleuthkitCase skCase, List<String> imagePat
 		final String imageKey = keyBuilder.toString();
 		
 		// Get the case identifier
-		try {
-			String caseIdentifier = skCase.getUniqueCaseIdentifier();
-		
-			synchronized (HandleCache.cacheLock) {
-				HandleCache.getCaseHandles(caseIdentifier).fsHandleCache.put(imageHandle, new HashMap<>());
-				HandleCache.getCaseHandles(caseIdentifier).imageHandleCache.put(imageKey, imageHandle);
-			}
-		} catch (TskCoreException ex) {
-			// getUniqueCaseIdentfier() will only fail if the case is closed
+		String caseIdentifier = skCase.getCaseHandleIdentifier();
+
+		synchronized (HandleCache.cacheLock) {
+			HandleCache.getCaseHandles(caseIdentifier).fsHandleCache.put(imageHandle, new HashMap<>());
+			HandleCache.getCaseHandles(caseIdentifier).imageHandleCache.put(imageKey, imageHandle);
 		}
 	}
 	
@@ -1045,7 +1036,7 @@ static long openPool(long imgHandle, long offset, SleuthkitCase skCase) throws T
 				if (skCase == null) {
 					caseIdentifier = HandleCache.getDefaultCaseIdentifier();
 				} else {
-					caseIdentifier = skCase.getUniqueCaseIdentifier();
+					caseIdentifier = skCase.getCaseHandleIdentifier();
 				}
 				
 				// If a pool handle cache for this image does not exist, make one
@@ -1092,7 +1083,7 @@ public static long openFs(long imgHandle, long fsOffset, SleuthkitCase skCase) t
 				if (skCase == null) {
 					caseIdentifier = HandleCache.getDefaultCaseIdentifier();
 				} else {
-					caseIdentifier = skCase.getUniqueCaseIdentifier();
+					caseIdentifier = skCase.getCaseHandleIdentifier();
 				}
 				final Map<Long, Long> imgOffSetToFsHandle = HandleCache.getCaseHandles(caseIdentifier).fsHandleCache.get(imgHandle);
 				if (imgOffSetToFsHandle == null) {
@@ -1143,7 +1134,7 @@ static long openFsPool(long imgHandle, long fsOffset, long poolHandle, long pool
 				if (skCase == null) {
 					caseIdentifier = HandleCache.getDefaultCaseIdentifier();
 				} else {
-					caseIdentifier = skCase.getUniqueCaseIdentifier();
+					caseIdentifier = skCase.getCaseHandleIdentifier();
 				}
 				final Map<Long, Long> imgOffSetToFsHandle = HandleCache.getCaseHandles(caseIdentifier).fsHandleCache.get(imgHandle);
 				if (imgOffSetToFsHandle == null) {
@@ -1200,7 +1191,7 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 			if (skCase == null) {
 				caseIdentifier = HandleCache.getDefaultCaseIdentifier();
 			} else {
-				caseIdentifier = skCase.getUniqueCaseIdentifier();
+				caseIdentifier = skCase.getCaseHandleIdentifier();
 			}
 			if (HandleCache.getCaseHandles(caseIdentifier).poolFsList.contains(fsHandle)) {
 				withinPool = true;
@@ -1224,7 +1215,7 @@ public static long openFile(long fsHandle, long fileId, TSK_FS_ATTR_TYPE_ENUM at
 				if (skCase == null) {
 					caseIdentifier = HandleCache.getDefaultCaseIdentifier();
 				} else {
-					caseIdentifier = skCase.getUniqueCaseIdentifier();
+					caseIdentifier = skCase.getCaseHandleIdentifier();
 				}
 				HandleCache.addFileHandle(caseIdentifier, fileHandle, fsHandle);
 
diff --git a/case-uco/java/README.md b/case-uco/java/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..007014b826dcf04f3356e4ab395bf9a09524fa4e
--- /dev/null
+++ b/case-uco/java/README.md
@@ -0,0 +1,16 @@
+# Sleuth Kit CASE JSON Support
+This package supports exporting Sleuth Kit DataModel objects to Cyber-investigation Analysis Standard Expression (CASE). 
+
+Clients will interface with the CaseUcoExporter class. This class contains methods to export most DataModel objects present in the Sleuth Kit Java Bindings. 
+
+**DISCLAIMER**: All API's in this package are subject to change.
+
+# Building the JAR file
+To build the JAR file, simply run '**ant jar**' in the case-uco/java folder. Alternatively, you can add the code to a NetBeans project and build using the regular 'build' action.
+
+# Configuration Properties
+Some behavior of the exporter can be configured via a Java Properties object. See the table below for available configuration properties.
+
+| Parameter | Description | Default |
+| :---: | :---: | :---: |
+| exporter.relationships.includeParentChild | Include or exclude parent-child relationships from the CASE output. By default, this class will export all parent-child relationships present in The Sleuth Kit DataModel. Volume System to Volume would be an example of such a relationship. If your use case requires exporting only the Volume, this configuration property will toggle that behavior. | true |    
diff --git a/case-uco/java/nbproject/project.properties b/case-uco/java/nbproject/project.properties
old mode 100755
new mode 100644
index 67264595338ebf0b3207ea5793314de0feebd417..46111105f3f39d5e2c299a860bd3a3a5f62eb9b4
--- a/case-uco/java/nbproject/project.properties
+++ b/case-uco/java/nbproject/project.properties
@@ -31,14 +31,14 @@ dist.javadoc.dir=${dist.dir}/javadoc
 endorsed.classpath=
 excludes=
 file.reference.gson-2.8.5.jar=lib/gson-2.8.5.jar
-file.reference.sleuthkit-4.9.0.jar=lib/sleuthkit-4.9.0.jar
+file.reference.sleuthkit-4.10.0.jar=lib/sleuthkit-4.10.0.jar
 includes=**
 jar.archive.disabled=${jnlp.enabled}
 jar.compress=false
 jar.index=${jnlp.enabled}
 javac.classpath=\
     ${file.reference.gson-2.8.5.jar}:\
-    ${file.reference.sleuthkit-4.9.0.jar}
+${file.reference.sleuthkit-4.10.0.jar}
 # Space-separated list of extra javac options
 javac.compilerargs=-Xlint
 javac.deprecation=false
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/Annotation.java b/case-uco/java/src/org/sleuthkit/caseuco/Annotation.java
index 4d4ade8da662a133f273acce25ab99a6b0749ba2..ef5e3bbfaa422901205b4ab8f56d8de746d97cb3 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/Annotation.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/Annotation.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.caseuco;
 
+import com.google.gson.annotations.SerializedName;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -27,18 +28,19 @@
  */
 class Annotation extends UcoObject {
 
-    private final List<String> tag;
+    @SerializedName("tag")
+    private final List<String> tags;
 
     private final List<String> object;
 
     Annotation(String uuid) {
         super(uuid, "Annotation");
-        tag = new ArrayList<>();
+        tags = new ArrayList<>();
         object = new ArrayList<>();
     }
 
     Annotation addTag(String tag) {
-        this.tag.add(tag);
+        this.tags.add(tag);
         return this;
     }
 
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/BrowserBookmark.java b/case-uco/java/src/org/sleuthkit/caseuco/BrowserBookmark.java
index 89c61afaa62c1930ce4e3c1f198c74a82ee36028..8e72ff5c27a7b791048507b1784ce5b6c1548598 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/BrowserBookmark.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/BrowserBookmark.java
@@ -26,8 +26,6 @@ class BrowserBookmark extends Facet {
 
     private String urlTargeted;
 
-    private String createdTime;
-
     private String application;
 
     BrowserBookmark() {
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoExporter.java b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoExporter.java
index 5c72521d231dbb1fdd9998d7a2be405f0ff36fa8..706e33c3aa6afc68fbf50e6e75a640c95921ff00 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoExporter.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoExporter.java
@@ -18,9 +18,13 @@
  */
 package org.sleuthkit.caseuco;
 
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.Properties;
 
 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT;
 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED;
@@ -87,17 +91,31 @@
 import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
 import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
 import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
-
 import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskData.DbType;
 
 /**
- * Exports Sleuthkit DataModel objects to CASE. UcoObject is the base class for
- * all CASE constructs. The export objects are configured to be serialized with
- * Gson.
+ * Exports Sleuth Kit DataModel objects to CASE. The CASE JSON output is
+ * configured to be serialized with Gson. Each export method will produce a list
+ * of CASE JSON objects. Clients should loop through this list and write these
+ * objects to any OutputStream via Gson. See the Gson documentation for more
+ * information on object serialization.
+ *
+ * NOTE: The exporter behavior can be configured by passing configuration
+ * parameters in a custom Properties instance. A list of available configuration
+ * properties can be found in the README.md file.
  */
 public class CaseUcoExporter {
 
-    private final CaseUcoUUIDService uuidService;
+    private static final String INCLUDE_PARENT_CHILD_RELATIONSHIPS_PROP = "exporter.relationships.includeParentChild";
+    private static final String DEFAULT_PARENT_CHILD_RELATIONSHIPS_VALUE = "true";
+
+    private final Gson gson;
+
+    private final SleuthkitCase sleuthkitCase;
+    private CaseUcoUUIDService uuidService;
+
+    private Properties props;
 
     /**
      * Creates a default CaseUcoExporter.
@@ -106,36 +124,113 @@ public class CaseUcoExporter {
      * be exported.
      */
     public CaseUcoExporter(SleuthkitCase sleuthkitCase) {
-        this.uuidService = new CaseUcoUUIDServiceImpl(sleuthkitCase);
+        this(sleuthkitCase, new Properties());
+    }
+
+    /**
+     * Creates a CaseUcoExporter configured to the properties present in the
+     * Properties instance.
+     *
+     * A list of available configuration properties can be found in the
+     * README.md file.
+     *
+     * @param sleuthkitCase The sleuthkit case instance containing the data to
+     * be exported.
+     * @param props Properties instance containing supported configuration
+     * parameters.
+     */
+    public CaseUcoExporter(SleuthkitCase sleuthkitCase, Properties props) {
+        this.sleuthkitCase = sleuthkitCase;
+        this.props = props;
+        this.setUUIDService(new CaseUcoUUIDServiceImpl(sleuthkitCase));
+        this.gson = new Gson();
     }
 
     /**
      * Overrides the default UUID implementation, which is used to generate the
      * unique @id properties in the CASE output. Some use cases may require a
-     * different value for @id, such as a web service (where this value
-     * should contain a URL).
+     * different value for @id, such as a web service (where this value should
+     * contain a URL).
      *
      * @param uuidService A custom UUID implementation, which will be used to
      * generate @id values in all export methods.
+     *
+     * @return reference to this, for chaining configuration method calls.
      */
-    public CaseUcoExporter(CaseUcoUUIDService uuidService) {
+    public final CaseUcoExporter setUUIDService(CaseUcoUUIDService uuidService) {
         this.uuidService = uuidService;
+        return this;
+    }
+
+    /**
+     * Exports the SleuthkitCase instance passed during initialization to CASE.
+     *
+     * @return A collection of CASE JSON elements
+     *
+     * @throws TskCoreException If an error occurred during database access.
+     */
+    public List<JsonElement> exportSleuthkitCase() throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
+        String caseDirPath = sleuthkitCase
+                .getDbDirPath()
+                .replaceAll("\\\\", "/");
+
+        Trace export = new Trace(this.uuidService.createUUID(sleuthkitCase));
+
+        if (sleuthkitCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
+            export.addBundle(new File()
+                    .setFilePath(caseDirPath)
+                    .setIsDirectory(true));
+        } else {
+            export.addBundle(new File()
+                    .setFilePath(caseDirPath + "/" + sleuthkitCase.getDatabaseName())
+                    .setIsDirectory(false));
+        }
+
+        addToOutput(export, output);
+        return output;
     }
 
     /**
      * Exports an AbstractFile instance to CASE.
      *
      * @param file AbstractFile instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
      *
-     * @throws TskCoreException
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportAbstractFile(AbstractFile file) throws TskCoreException {
-        Trace export = new Trace(this.uuidService.createUUID(file))
-                .addBundle(new ContentData()
-                        .setMimeType(file.getMIMEType())
-                        .setSizeInBytes(file.getSize())
-                        .setMd5Hash(file.getMd5Hash()));
+    public List<JsonElement> exportAbstractFile(AbstractFile file) throws TskCoreException {
+        return exportAbstractFile(file, null);
+    }
+
+    /**
+     * Exports an AbstractFile instance to CASE.
+     *
+     * @param file AbstractFile instance to export
+     * @param localPath The location of the file on secondary storage, somewhere
+     * other than the case. Example: local disk. This value will be ignored if
+     * null
+     * @return A collection of CASE JSON elements
+     *
+     * @throws TskCoreException If an error occurred during database access.
+     */
+    public List<JsonElement> exportAbstractFile(AbstractFile file, String localPath) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
+        ContentData contentData = new ContentData()
+                .setMimeType(file.getMIMEType())
+                .setSizeInBytes(file.getSize())
+                .setMd5Hash(file.getMd5Hash());
+
+        if (localPath != null) {
+            Trace localPathTrace = new BlankTraceNode()
+                    .addBundle(new URL()
+                            .setFullValue(localPath));
+            contentData.setDataPayloadReferenceUrl(localPathTrace);
+
+            addToOutput(localPathTrace, output);
+        }
 
         File fileExport = new File()
                 .setAccessedTime(file.getAtime())
@@ -147,40 +242,57 @@ public UcoObject exportAbstractFile(AbstractFile file) throws TskCoreException {
         fileExport.setModifiedTime(file.getMtime());
         fileExport.setCreatedTime(file.getCrtime());
 
-        export.addBundle(fileExport);
+        Trace export = new Trace(this.uuidService.createUUID(file))
+                .addBundle(contentData)
+                .addBundle(fileExport);
+
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(file.getDataSource()));
 
-        return export;
+        return output;
     }
 
     /**
      * Exports a ContentTag instance to CASE.
      *
      * @param contentTag ContentTag instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportContentTag(ContentTag contentTag) {
+    public List<JsonElement> exportContentTag(ContentTag contentTag) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Annotation annotation = new Annotation(this.uuidService.createUUID(contentTag))
                 .addObject(this.uuidService.createUUID(contentTag.getContent()));
         annotation.setDescription(contentTag.getComment());
         annotation.addTag(contentTag.getName().getDisplayName());
 
-        return annotation;
+        addToOutput(annotation, output);
+        return output;
     }
 
     /**
      * Exports a DataSource instance to CASE.
      *
      * @param dataSource DataSource instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportDataSource(DataSource dataSource) {
+    public List<JsonElement> exportDataSource(DataSource dataSource) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Trace export = new Trace(this.uuidService.createUUID(dataSource))
                 .addBundle(new File()
                         .setFilePath(getDataSourcePath(dataSource)))
                 .addBundle(new ContentData()
                         .setSizeInBytes(dataSource.getSize()));
 
-        return export;
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(this.sleuthkitCase));
+
+        return output;
     }
 
     String getDataSourcePath(DataSource dataSource) {
@@ -201,38 +313,56 @@ String getDataSourcePath(DataSource dataSource) {
      * Exports a FileSystem instance to CASE.
      *
      * @param fileSystem FileSystem instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportFileSystem(FileSystem fileSystem) {
+    public List<JsonElement> exportFileSystem(FileSystem fileSystem) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Trace export = new Trace(this.uuidService.createUUID(fileSystem))
                 .addBundle(new org.sleuthkit.caseuco.FileSystem()
                         .setFileSystemType(fileSystem.getFsType())
                         .setCluserSize(fileSystem.getBlock_size()));
 
-        return export;
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(fileSystem.getParent()));
+
+        return output;
     }
 
     /**
      * Exports a Pool instance to CASE.
      *
      * @param pool Pool instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     *
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportPool(Pool pool) {
+    public List<JsonElement> exportPool(Pool pool) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Trace export = new Trace(this.uuidService.createUUID(pool))
                 .addBundle(new ContentData()
                         .setSizeInBytes(pool.getSize()));
 
-        return export;
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(pool.getParent()));
+
+        return output;
     }
 
     /**
      * Exports a Volume instance to CASE.
      *
      * @param volume Volume instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportVolume(Volume volume) {
+    public List<JsonElement> exportVolume(Volume volume) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Trace export = new Trace(this.uuidService.createUUID(volume));
         org.sleuthkit.caseuco.Volume volumeFacet = new org.sleuthkit.caseuco.Volume();
         if (volume.getLength() > 0) {
@@ -242,7 +372,11 @@ public UcoObject exportVolume(Volume volume) {
                 .addBundle(new ContentData()
                         .setSizeInBytes(volume.getSize()));
 
-        return export;
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(volume.getParent()));
+
+        return output;
 
     }
 
@@ -250,30 +384,39 @@ public UcoObject exportVolume(Volume volume) {
      * Exports a VolumeSystem instance to CASE.
      *
      * @param volumeSystem VolumeSystem instance to export
-     * @return Equivalent CASE construction
+     * @return A collection of CASE JSON elements
+     *
+     * @throws TskCoreException If an error occurred during database access.
      */
-    public UcoObject exportVolumeSystem(VolumeSystem volumeSystem) {
+    public List<JsonElement> exportVolumeSystem(VolumeSystem volumeSystem) throws TskCoreException {
+        List<JsonElement> output = new ArrayList<>();
+
         Trace export = new Trace(this.uuidService.createUUID(volumeSystem))
                 .addBundle(new ContentData()
                         .setSizeInBytes(volumeSystem.getSize()));
 
-        return export;
+        addToOutput(export, output);
+        addParentChildRelationship(output, export.getId(),
+                this.uuidService.createUUID(volumeSystem.getParent()));
+
+        return output;
     }
 
     /**
      * Exports a BlackboardArtifact instance to CASE.
      *
      * @param artifact BlackboardArtifact instance to export
-     * @return Equivalent CASE construction(s)
-     * @throws org.sleuthkit.datamodel.TskCoreException
-     * @throws org.sleuthkit.caseuco.ContentNotExportableException if the
-     * content could not be exported, even in part, to CASE.
-     * @throws
-     * org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException
+     * @return A collection of CASE JSON elements
+     *
+     * @throws TskCoreException If an error occurred during database access.
+     * @throws ContentNotExportableException if the content could not be
+     * exported, even in part, to CASE.
+     * @throws BlackboardJsonAttrUtil.InvalidJsonException If a JSON valued
+     * attribute could not be correctly deserialized.
      */
-    public List<UcoObject> exportBlackboardArtifact(BlackboardArtifact artifact) throws TskCoreException,
+    public List<JsonElement> exportBlackboardArtifact(BlackboardArtifact artifact) throws TskCoreException,
             ContentNotExportableException, BlackboardJsonAttrUtil.InvalidJsonException {
-        List<UcoObject> output = new ArrayList<>();
+        List<JsonElement> output = new ArrayList<>();
 
         String uuid = this.uuidService.createUUID(artifact);
         int artifactTypeId = artifact.getArtifactTypeID();
@@ -378,14 +521,19 @@ public List<UcoObject> exportBlackboardArtifact(BlackboardArtifact artifact) thr
             assembleGpsTrack(uuid, artifact, output);
         }
 
-        if (!output.isEmpty()) {
-            return output;
+        if (output.isEmpty()) {
+            throw new ContentNotExportableException(String.format(
+                    "Artifact [id:%d, type:%d] is either not supported "
+                    + "or did not have any exported attributes.", artifact.getId(), artifactTypeId));
         }
 
-        throw new ContentNotExportableException();
+        addParentChildRelationship(output, uuid,
+                this.uuidService.createUUID(artifact.getParent()));
+
+        return output;
     }
 
-    private void assembleWebCookie(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebCookie(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new URL()
                         .setFullValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_URL)))
@@ -410,12 +558,12 @@ private void assembleWebCookie(String uuid, BlackboardArtifact artifact, List<Uc
 
         export.addBundle(cookie);
 
-        output.add(export);
-        output.add(cookieDomainNode);
-        output.add(applicationNode);
+        addToOutput(export, output);
+        addToOutput(cookieDomainNode, output);
+        addToOutput(applicationNode, output);
     }
 
-    private void assembleWebBookmark(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebBookmark(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace applicationNode = new BlankTraceNode()
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
@@ -431,16 +579,16 @@ private void assembleWebBookmark(String uuid, BlackboardArtifact artifact, List<
                 .addBundle(new DomainName()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DOMAIN)));
 
-        output.add(export);
-        output.add(applicationNode);
+        addToOutput(export, output);
+        addToOutput(applicationNode, output);
     }
 
-    private void assembleGenInfo(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleGenInfo(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Hash hash = new Hash(uuid, getValueIfPresent(artifact, StandardAttributeTypes.TSK_HASH_PHOTODNA));
-        output.add(hash);
+        addToOutput(hash, output);
     }
 
-    private void assembleWebHistory(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebHistory(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace userNameNode = new BlankTraceNode();
 
         IdentityFacet identityFacet = new IdentityFacet();
@@ -456,11 +604,11 @@ private void assembleWebHistory(String uuid, BlackboardArtifact artifact, List<U
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
 
-        output.add(export);
-        output.add(userNameNode);
+        addToOutput(export, output);
+        addToOutput(userNameNode, output);
     }
 
-    private void assembleWebDownload(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebDownload(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new URL()
                         .setFullValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_URL)))
@@ -470,10 +618,10 @@ private void assembleWebDownload(String uuid, BlackboardArtifact artifact, List<
                         .setFilePath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PATH)))
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleDeviceAttached(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleDeviceAttached(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Device()
                         .setManufacturer(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DEVICE_MAKE))
@@ -483,18 +631,18 @@ private void assembleDeviceAttached(String uuid, BlackboardArtifact artifact, Li
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_MAC_ADDRESS)));
 
         export.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleHashsetHit(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleHashsetHit(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid);
         export.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_SET_NAME));
         export.setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleInstalledProg(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleInstalledProg(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new File()
                         .setFilePath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PATH_SOURCE)));
@@ -509,10 +657,10 @@ private void assembleInstalledProg(String uuid, BlackboardArtifact artifact, Lis
         file.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME_CREATED));
         export.addBundle(file);
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleRecentObject(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleRecentObject(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
@@ -529,32 +677,31 @@ private void assembleRecentObject(String uuid, BlackboardArtifact artifact, List
 
         export.addBundle(file);
 
-        output.add(export);
+        addToOutput(export, output);
 
         Assertion assertion = new BlankAssertionNode()
                 .setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
-        output.add(assertion);
-
-        output.add(new BlankRelationshipNode()
+        addToOutput(assertion, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(assertion.getId())
-                .setTarget(uuid));
+                .setTarget(uuid), output);
     }
 
-    private void assembleInterestingFileHit(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleInterestingFileHit(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid);
         export.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_SET_NAME));
         export.setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleExtractedText(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleExtractedText(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new ExtractedString()
                         .setStringValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_TEXT)));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleEmailMessage(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleEmailMessage(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace bccNode = new BlankTraceNode()
                 .addBundle(new EmailAddress()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_EMAIL_BCC)));
@@ -600,14 +747,14 @@ private void assembleEmailMessage(String uuid, BlackboardArtifact artifact, List
                 .addBundle(new File()
                         .setFilePath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PATH)));
 
-        output.add(export);
-        output.add(bccNode);
-        output.add(ccNode);
-        output.add(fromNode);
-        output.add(headerRawNode);
+        addToOutput(export, output);
+        addToOutput(bccNode, output);
+        addToOutput(ccNode, output);
+        addToOutput(fromNode, output);
+        addToOutput(headerRawNode, output);
     }
 
-    private void assembleWebSearchQuery(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebSearchQuery(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace applicationNode = new BlankTraceNode()
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
@@ -619,11 +766,11 @@ private void assembleWebSearchQuery(String uuid, BlackboardArtifact artifact, Li
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DOMAIN)))
                 .addBundle(new ApplicationAccount()
                         .setApplication(applicationNode));
-        output.add(export);
-        output.add(applicationNode);
+        addToOutput(export, output);
+        addToOutput(applicationNode, output);
     }
 
-    private void assembleOsInfo(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleOsInfo(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Identity registeredOwnerNode = new BlankIdentityNode();
         registeredOwnerNode.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_OWNER));
         Identity registeredOrganizationNode = new BlankIdentityNode();
@@ -653,13 +800,14 @@ private void assembleOsInfo(String uuid, BlackboardArtifact artifact, List<UcoOb
                         .setRegisteredOrganization(registeredOrganizationNode)
                         .setRegisteredOwner(registeredOwnerNode)
                         .setWindowsTempDirectory(tempDirectoryNode));
-        output.add(export);
-        output.add(registeredOwnerNode);
-        output.add(registeredOrganizationNode);
-        output.add(tempDirectoryNode);
+
+        addToOutput(export, output);
+        addToOutput(registeredOwnerNode, output);
+        addToOutput(registeredOrganizationNode, output);
+        addToOutput(tempDirectoryNode, output);
     }
 
-    private void assembleOsAccount(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleOsAccount(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new EmailAddress()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_EMAIL)))
@@ -688,11 +836,11 @@ private void assembleOsAccount(String uuid, BlackboardArtifact artifact, List<Uc
 
         export.addBundle(account);
 
-        output.add(export);
-        output.add(ownerNode);
+        addToOutput(export, output);
+        addToOutput(ownerNode, output);
     }
 
-    private void assembleServiceAccount(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleServiceAccount(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace inReplyToNode = new BlankTraceNode()
                 .addBundle(new EmailAddress()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_EMAIL_REPLYTO)));
@@ -727,12 +875,12 @@ private void assembleServiceAccount(String uuid, BlackboardArtifact artifact, Li
         account.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME_CREATED));
         export.addBundle(account);
 
-        output.add(export);
-        output.add(applicationNode);
-        output.add(inReplyToNode);
+        addToOutput(export, output);
+        addToOutput(applicationNode, output);
+        addToOutput(inReplyToNode, output);
     }
 
-    private void assembleContact(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleContact(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         EmailAddress homeAddress = new EmailAddress()
                 .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_EMAIL_HOME));
         homeAddress.setTag("Home");
@@ -767,10 +915,10 @@ private void assembleContact(String uuid, BlackboardArtifact artifact, List<UcoO
                 .addBundle(homePhone)
                 .addBundle(workPhone)
                 .addBundle(mobilePhone);
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleMessage(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException, BlackboardJsonAttrUtil.InvalidJsonException {
+    private void assembleMessage(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException, BlackboardJsonAttrUtil.InvalidJsonException {
         Trace applicationNode = new BlankTraceNode()
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_MESSAGE_TYPE)));
@@ -818,14 +966,14 @@ private void assembleMessage(String uuid, BlackboardArtifact artifact, List<UcoO
             });
         }
 
-        output.add(export);
-        output.add(applicationNode);
-        output.add(senderNode);
-        output.add(fromNode);
-        output.add(toNode);
+        addToOutput(export, output);
+        addToOutput(applicationNode, output);
+        addToOutput(senderNode, output);
+        addToOutput(fromNode, output);
+        addToOutput(toNode, output);
     }
 
-    private void assembleMetadataExif(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleMetadataExif(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Device()
                         .setManufacturer(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DEVICE_MAKE))
@@ -836,10 +984,10 @@ private void assembleMetadataExif(String uuid, BlackboardArtifact artifact, List
                         .setLongitude(getDoubleIfPresent(artifact, StandardAttributeTypes.TSK_GEO_LONGITUDE)));
 
         export.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME_CREATED));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleCallog(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleCallog(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace fromNode = new BlankTraceNode()
                 .addBundle(new PhoneAccount()
                         .setPhoneNumber(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PHONE_NUMBER_FROM)));
@@ -860,12 +1008,12 @@ private void assembleCallog(String uuid, BlackboardArtifact artifact, List<UcoOb
                 .addBundle(new Contact()
                         .setContactName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_NAME)));
 
-        output.add(export);
-        output.add(toNode);
-        output.add(fromNode);
+        addToOutput(export, output);
+        addToOutput(toNode, output);
+        addToOutput(fromNode, output);
     }
 
-    private void assembleCalendarEntry(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleCalendarEntry(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid);
 
         CalendarEntry calendarEntry = new CalendarEntry()
@@ -881,21 +1029,21 @@ private void assembleCalendarEntry(String uuid, BlackboardArtifact artifact, Lis
         calendarEntry.setLocation(locationNode);
         export.addBundle(calendarEntry);
 
-        output.add(export);
-        output.add(locationNode);
+        addToOutput(export, output);
+        addToOutput(locationNode, output);
     }
 
-    private void assembleSpeedDialEntry(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleSpeedDialEntry(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Contact()
                         .setContactName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_NAME_PERSON)))
                 .addBundle(new PhoneAccount()
                         .setPhoneNumber(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PHONE_NUMBER)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleBluetoothPairing(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleBluetoothPairing(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new MobileDevice()
                         .setBluetoothDeviceName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DEVICE_NAME)))
@@ -903,10 +1051,10 @@ private void assembleBluetoothPairing(String uuid, BlackboardArtifact artifact,
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_MAC_ADDRESS)));
 
         export.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleGpsBookmark(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleGpsBookmark(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new LatLongCoordinates()
                         .setAltitude(getDoubleIfPresent(artifact, StandardAttributeTypes.TSK_GEO_ALTITUDE))
@@ -921,10 +1069,10 @@ private void assembleGpsBookmark(String uuid, BlackboardArtifact artifact, List<
 
         export.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME));
         export.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_NAME));
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleGpsLastKnownLocation(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleGpsLastKnownLocation(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new LatLongCoordinates()
                         .setAltitude(getDoubleIfPresent(artifact, StandardAttributeTypes.TSK_GEO_ALTITUDE))
@@ -939,14 +1087,14 @@ private void assembleGpsLastKnownLocation(String uuid, BlackboardArtifact artifa
         simpleAddress.setDescription(getValueIfPresent(artifact, StandardAttributeTypes.TSK_LOCATION));
         export.addBundle(simpleAddress);
 
-        output.add(export);
-        output.add(locationNode);
-        output.add(new BlankRelationshipNode()
+        addToOutput(export, output);
+        addToOutput(locationNode, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(locationNode.getId())
-                .setTarget(export.getId()));
+                .setTarget(export.getId()), output);
     }
 
-    private void assembleGpsSearch(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleGpsSearch(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new LatLongCoordinates()
                         .setAltitude(getDoubleIfPresent(artifact, StandardAttributeTypes.TSK_GEO_ALTITUDE))
@@ -961,30 +1109,30 @@ private void assembleGpsSearch(String uuid, BlackboardArtifact artifact, List<Uc
         simpleAddress.setDescription(getValueIfPresent(artifact, StandardAttributeTypes.TSK_LOCATION));
         export.addBundle(simpleAddress);
 
-        output.add(export);
-        output.add(locationNode);
-        output.add(new BlankRelationshipNode()
+        addToOutput(export, output);
+        addToOutput(locationNode, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(locationNode.getId())
-                .setTarget(export.getId()));
+                .setTarget(export.getId()), output);
     }
 
-    private void assembleProgRun(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleProgRun(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME))
                         .setNumberOfLaunches(getIntegerIfPresent(artifact, StandardAttributeTypes.TSK_COUNT)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleEncryptionDetected(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleEncryptionDetected(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid)
                 .setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleInterestingArtifact(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleInterestingArtifact(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid);
         export.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_SET_NAME));
         export.setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
@@ -993,15 +1141,15 @@ private void assembleInterestingArtifact(String uuid, BlackboardArtifact artifac
         if (associatedArtifactId != null) {
             BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(associatedArtifactId);
 
-            output.add(new BlankRelationshipNode()
+            addToOutput(new BlankRelationshipNode()
                     .setSource(export.getId())
-                    .setTarget(this.uuidService.createUUID(associatedArtifact)));
+                    .setTarget(this.uuidService.createUUID(associatedArtifact)), output);
         }
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleGPSRoute(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleGPSRoute(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
@@ -1014,24 +1162,24 @@ private void assembleGPSRoute(String uuid, BlackboardArtifact artifact, List<Uco
         Location location = new BlankLocationNode();
         location.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_NAME));
 
-        output.add(export);
-        output.add(location);
-        output.add(new BlankRelationshipNode()
+        addToOutput(export, output);
+        addToOutput(location, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(location.getId())
-                .setTarget(export.getId()));
+                .setTarget(export.getId()), output);
     }
 
-    private void assembleRemoteDrive(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleRemoteDrive(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new PathRelation()
                         .setPath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_REMOTE_PATH)))
                 .addBundle(new PathRelation()
                         .setPath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_LOCAL_PATH)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleAccount(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleAccount(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Account account = new Account()
                 .setAccountType(getValueIfPresent(artifact, StandardAttributeTypes.TSK_ACCOUNT_TYPE))
                 .setAccountIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_ID));
@@ -1044,25 +1192,25 @@ private void assembleAccount(String uuid, BlackboardArtifact artifact, List<UcoO
                 .addBundle(account)
                 .addBundle(creditCardAccount);
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleEncryptionSuspected(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleEncryptionSuspected(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid)
                 .setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleObjectDetected(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleObjectDetected(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid)
                 .setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
         export.setDescription(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DESCRIPTION));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleWifiNetwork(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWifiNetwork(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         WirelessNetworkConnection wirelessNetwork = new WirelessNetworkConnection()
                 .setSSID(getValueIfPresent(artifact, StandardAttributeTypes.TSK_SSID));
 
@@ -1076,10 +1224,10 @@ private void assembleWifiNetwork(String uuid, BlackboardArtifact artifact, List<
         Trace export = new Trace(uuid)
                 .addBundle(wirelessNetwork);
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleDeviceInfo(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleDeviceInfo(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new MobileDevice()
                         .setIMEI(getValueIfPresent(artifact, StandardAttributeTypes.TSK_IMEI)))
@@ -1087,49 +1235,49 @@ private void assembleDeviceInfo(String uuid, BlackboardArtifact artifact, List<U
                         .setICCID(getValueIfPresent(artifact, StandardAttributeTypes.TSK_ICCID))
                         .setIMSI(getValueIfPresent(artifact, StandardAttributeTypes.TSK_IMSI)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleSimAttached(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleSimAttached(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new SIMCard()
                         .setICCID(getValueIfPresent(artifact, StandardAttributeTypes.TSK_ICCID))
                         .setIMSI(getValueIfPresent(artifact, StandardAttributeTypes.TSK_IMSI)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleBluetoothAdapter(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleBluetoothAdapter(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new MACAddress()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_MAC_ADDRESS)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleWifiNetworkAdapter(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWifiNetworkAdapter(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new MACAddress()
                         .setValue(getValueIfPresent(artifact, StandardAttributeTypes.TSK_MAC_ADDRESS)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleVerificationFailed(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleVerificationFailed(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid);
         export.setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleDataSourceUsage(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleDataSourceUsage(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid);
         export.setDescription(getValueIfPresent(artifact, StandardAttributeTypes.TSK_DESCRIPTION));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleWebFormAddress(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebFormAddress(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         SimpleAddress simpleAddress = new SimpleAddress();
         simpleAddress.setDescription(getValueIfPresent(artifact, StandardAttributeTypes.TSK_LOCATION));
 
@@ -1146,15 +1294,15 @@ private void assembleWebFormAddress(String uuid, BlackboardArtifact artifact, Li
         Person person = new BlankPersonNode();
         person.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_NAME_PERSON));
 
-        output.add(export);
-        output.add(person);
-        output.add(new BlankRelationshipNode()
+        addToOutput(export, output);
+        addToOutput(person, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(person.getId())
-                .setTarget(export.getId()));
+                .setTarget(export.getId()), output);
 
     }
 
-    private void assembleWebCache(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleWebCache(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new PathRelation()
                         .setPath(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PATH)))
@@ -1165,10 +1313,10 @@ private void assembleWebCache(String uuid, BlackboardArtifact artifact, List<Uco
 
         export.setCreatedTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME_CREATED));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleTimelineEvent(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleTimelineEvent(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Action export = new Action(uuid)
                 .setStartTime(getLongIfPresent(artifact, StandardAttributeTypes.TSK_DATETIME));
 
@@ -1184,48 +1332,48 @@ private void assembleTimelineEvent(String uuid, BlackboardArtifact artifact, Lis
                         .addBundle(new ActionArgument()
                                 .setArgumentName(timelineEventType.get().getDisplayName()));
 
-                output.add(actionArg);
-                output.add(new BlankRelationshipNode()
+                addToOutput(actionArg, output);
+                addToOutput(new BlankRelationshipNode()
                         .setSource(actionArg.getId())
-                        .setTarget(export.getId()));
+                        .setTarget(export.getId()), output);
             }
         }
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleClipboardContent(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleClipboardContent(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Note()
                         .setText(getValueIfPresent(artifact, StandardAttributeTypes.TSK_TEXT)));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleAssociatedObject(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleAssociatedObject(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid);
-        output.add(export);
+        addToOutput(export, output);
 
         BlackboardAttribute associatedArtifactID = artifact.getAttribute(StandardAttributeTypes.TSK_ASSOCIATED_ARTIFACT);
         if (associatedArtifactID != null) {
             long artifactID = associatedArtifactID.getValueLong();
             BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getArtifactByArtifactId(artifactID);
             if (associatedArtifact != null) {
-                output.add(new BlankRelationshipNode()
+                addToOutput(new BlankRelationshipNode()
                         .setSource(uuid)
-                        .setTarget(this.uuidService.createUUID(associatedArtifact)));
+                        .setTarget(this.uuidService.createUUID(associatedArtifact)), output);
             }
         }
     }
 
-    private void assembleUserContentSuspected(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleUserContentSuspected(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Assertion export = new Assertion(uuid);
         export.setStatement(getValueIfPresent(artifact, StandardAttributeTypes.TSK_COMMENT));
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
-    private void assembleMetadata(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException {
+    private void assembleMetadata(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException {
         Trace export = new Trace(uuid)
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME))
@@ -1253,19 +1401,19 @@ private void assembleMetadata(String uuid, BlackboardArtifact artifact, List<Uco
         lastAuthor.setTag("Last Author");
         lastAuthor.setName(getValueIfPresent(artifact, StandardAttributeTypes.TSK_USER_ID));
 
-        output.add(export);
-        output.add(owner);
-        output.add(organization);
-        output.add(new BlankRelationshipNode()
+        addToOutput(export, output);
+        addToOutput(owner, output);
+        addToOutput(organization, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(organization.getId())
-                .setTarget(export.getId()));
-        output.add(lastAuthor);
-        output.add(new BlankRelationshipNode()
+                .setTarget(export.getId()), output);
+        addToOutput(lastAuthor, output);
+        addToOutput(new BlankRelationshipNode()
                 .setSource(lastAuthor.getId())
-                .setTarget(export.getId()));
+                .setTarget(export.getId()), output);
     }
 
-    private void assembleGpsTrack(String uuid, BlackboardArtifact artifact, List<UcoObject> output) throws TskCoreException, BlackboardJsonAttrUtil.InvalidJsonException {
+    private void assembleGpsTrack(String uuid, BlackboardArtifact artifact, List<JsonElement> output) throws TskCoreException, BlackboardJsonAttrUtil.InvalidJsonException {
         Trace export = new Trace(uuid)
                 .addBundle(new Application()
                         .setApplicationIdentifier(getValueIfPresent(artifact, StandardAttributeTypes.TSK_PROG_NAME)));
@@ -1282,7 +1430,7 @@ private void assembleGpsTrack(String uuid, BlackboardArtifact artifact, List<Uco
             }
         }
 
-        output.add(export);
+        addToOutput(export, output);
     }
 
     /**
@@ -1335,4 +1483,28 @@ private String getValueIfPresent(BlackboardArtifact artifact, BlackboardAttribut
             return null;
         }
     }
+
+    /**
+     * Add the parent-child relationship, if configured to do so.
+     */
+    private void addParentChildRelationship(List<JsonElement> output, String sourceId, String parentId) {
+        String parentChildProperty = this.props.getProperty(INCLUDE_PARENT_CHILD_RELATIONSHIPS_PROP,
+                DEFAULT_PARENT_CHILD_RELATIONSHIPS_VALUE);
+
+        if (Boolean.valueOf(parentChildProperty)) {
+            addToOutput(new BlankRelationshipNode()
+                    .setSource(sourceId)
+                    .setTarget(parentId)
+                    .setKindOfRelationship("contained-within")
+                    .isDirectional(true), output);
+        }
+    }
+
+    /**
+     * Adds a given CASE export object to the JSON output that will be consumed
+     * by the client.
+     */
+    private void addToOutput(UcoObject ucoObject, List<JsonElement> output) {
+        output.add(gson.toJsonTree(ucoObject));
+    }
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDService.java b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDService.java
index 23d8265149ab7f8680bb5b7b647509a1f9814664..5bae8a470f9c08bd2ae631691e2a7063ecb6d7f0 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDService.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDService.java
@@ -20,22 +20,28 @@
 
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.ContentTag;
+import org.sleuthkit.datamodel.SleuthkitCase;
 
 /**
  * Providing a way to customize UUIDs is a necessary feature to promote the
- * reuse of the CaseUcoExporter class. IDs in a REST API use case would prefer
+ * reuse of the CaseUcoExporter class. For example, REST API IDs would prefer
  * links to the relevant endpoints and IDs in a standalone TSK application may
- * prefer a combination of object id, case id, and time stamp. A runtime object
- * that provides this service is a good way to address content objects that
- * reference other content objects. For example, when the CaesUcoExporter is
- * asked to export a TSK_ASSOCIATED_ARTIFACT_HIT or a ContentTag, it'll also
- * need to export the reference to the underlying content as well. It wouldn't
- * be very user friendly if we asked for these IDs up front, so instead it's
- * quite handy to have some service that can be set once and forgotten about
- * during execution.
+ * prefer a combination of object id, case name, and time stamp. A runtime
+ * object that provides this service is a nice 'hands-off' approach to exporting
+ * content that references other content objects. For example,
+ * TSK_ASSOCIATED_ARTIFACT_HIT and ContentTag both reference other content
+ * instances that need to be linked in the CASE output. Creating a class for
+ * this task guarantees consistent IDs and ensures the API is simple to use.
+ *
+ * CaseUcoExporter already ships with a default implementation of this class.
+ * The default implementation will use the object id and database name. To
+ * override the default, please refer to the CaseUcoExporter documentation.
  */
 public interface CaseUcoUUIDService {
 
     public String createUUID(Content content);
+
     public String createUUID(ContentTag contentTag);
+
+    public String createUUID(SleuthkitCase sleuthkitCase);
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDServiceImpl.java b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDServiceImpl.java
index 7b1fcd424588838eb7351e5c168e1df89cfe28a8..d42b0e89f5b2ca6a829d94a2ae292bbba1d4ac68 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDServiceImpl.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/CaseUcoUUIDServiceImpl.java
@@ -37,11 +37,16 @@ class CaseUcoUUIDServiceImpl implements CaseUcoUUIDService {
 
     @Override
     public String createUUID(Content content) {
-        return "content" + "-" + content.getId() + "_" + databaseName;
+        return "_:content-" + content.getId() + "_" + databaseName;
     }
 
     @Override
     public String createUUID(ContentTag contentTag) {
-        return "tag" + "-" + contentTag.getId() + "_" + databaseName;
+        return "_:tag-" + contentTag.getId() + "_" + databaseName;
+    }
+
+    @Override
+    public String createUUID(SleuthkitCase sleuthkitCase) {
+        return "_:case-" + sleuthkitCase.getDatabaseName();
     }
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/ContentData.java b/case-uco/java/src/org/sleuthkit/caseuco/ContentData.java
index 9d6e85485a64fb7d6c1de85f0fd7500bc5ab5b73..cf317eed75955477af7e57d0cca4ea2ee0a33169 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/ContentData.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/ContentData.java
@@ -40,6 +40,8 @@ class ContentData extends Facet {
     private String dataPayload;
 
     private String owner;
+    
+    private String dataPayloadReferenceUrl;
 
     ContentData() {
         super(ContentData.class.getSimpleName());
@@ -72,4 +74,9 @@ ContentData setOwner(Identity owner) {
         this.owner = owner.getId();
         return this;
     }
+    
+    ContentData setDataPayloadReferenceUrl(UcoObject url) {
+        this.dataPayloadReferenceUrl = url.getId();
+        return this;
+    }
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/ContentNotExportableException.java b/case-uco/java/src/org/sleuthkit/caseuco/ContentNotExportableException.java
index abb248ce9650f1dac409be952c53154c1b5f8892..4d16855cb5724f72223c7a2eb39d9f0cccc25480 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/ContentNotExportableException.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/ContentNotExportableException.java
@@ -23,4 +23,8 @@
  */
 public class ContentNotExportableException extends Exception {
     private static final long serialVersionUID = 1L;
+    
+    ContentNotExportableException(String msg) {
+        super(msg);
+    }
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/Message.java b/case-uco/java/src/org/sleuthkit/caseuco/Message.java
index e87ad819ab0eb912ab1d37e600fdd2410765c15a..fb61b9bea59901b4c8e2f758134fa5617f4fde0f 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/Message.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/Message.java
@@ -31,7 +31,7 @@ class Message extends Facet {
 
     private String application;
 
-    String sentTime;
+    private String sentTime;
 
     private String messageType;
 
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/Relationship.java b/case-uco/java/src/org/sleuthkit/caseuco/Relationship.java
index f8a18995f382c7944efafb4c67ea856e80e7dd52..27d4563fbb8c4a28a132aec826c90d3b0d8af631 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/Relationship.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/Relationship.java
@@ -27,6 +27,10 @@ class Relationship extends UcoObject {
     private String source;
 
     private String target;
+    
+    private String kindOfRelationship;
+    
+    private Boolean isDirectional;
 
     Relationship(String id) {
         super(id, "Relationship");
@@ -41,4 +45,14 @@ Relationship setTarget(String target) {
         this.target = target;
         return this;
     }
+    
+    Relationship setKindOfRelationship(String kindOfRelationship) {
+        this.kindOfRelationship = kindOfRelationship;
+        return this;
+    }
+    
+    Relationship isDirectional(boolean isDirectional) {
+        this.isDirectional = isDirectional;
+        return this;
+    }
 }
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/SMSMessage.java b/case-uco/java/src/org/sleuthkit/caseuco/SMSMessage.java
index 170ee6864867874155066e4f19db95d39fe5a1cc..c2012bd8c6b485d4be83e59da8bb3309252a16a2 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/SMSMessage.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/SMSMessage.java
@@ -24,7 +24,7 @@
  */
 class SMSMessage extends Facet {
 
-    Boolean isRead;
+    private Boolean isRead;
 
     SMSMessage() {
         super(SMSMessage.class.getSimpleName());
diff --git a/case-uco/java/src/org/sleuthkit/caseuco/UcoObject.java b/case-uco/java/src/org/sleuthkit/caseuco/UcoObject.java
index a87917ea57383c33c4237b36ce7109e650e6ee07..14c49bee68092898c292d5a6479278fdda9ea6a6 100755
--- a/case-uco/java/src/org/sleuthkit/caseuco/UcoObject.java
+++ b/case-uco/java/src/org/sleuthkit/caseuco/UcoObject.java
@@ -25,7 +25,7 @@
 /**
  * Base class for all CASE/UCO constructs.
  */
-public abstract class UcoObject {
+abstract class UcoObject {
 
     @SerializedName("@id")
     private String id;
diff --git a/configure.ac b/configure.ac
index 17845711871eafde8ce7a41b9a0b8dca4c3152c8..8e76b034703b9610b6f972743e441914b15ed83c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script.
 
 AC_PREREQ(2.59)
 
-AC_INIT(sleuthkit, 4.9.0)
+AC_INIT(sleuthkit, 4.10.0)
 m4_include([m4/ax_pthread.m4])
 dnl include the version from 1.12.1. This will work for
 m4_include([m4/cppunit.m4])
diff --git a/debian/changelog b/debian/changelog
index 1690c7fd522c0cf459a15909a66e8f8ce6cfb6e9..bd9a1dbe3d50a60f453d194f39ace14d0b961e90 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-sleuthkit-java (4.9.0-1) unstable; urgency=medium
+sleuthkit-java (4.10.0-1) unstable; urgency=medium
 
   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
 
diff --git a/debian/sleuthkit-java.install b/debian/sleuthkit-java.install
index 9bf890260a185733786c51afbd4b3a0d7f08653d..7def1fbba803358845dc7f5b29ecfe8f2e1fac5e 100644
--- a/debian/sleuthkit-java.install
+++ b/debian/sleuthkit-java.install
@@ -1,4 +1,4 @@
 bindings/java/lib/sqlite-jdbc-3.25.2.jar /usr/share/java
-bindings/java/dist/sleuthkit-4.9.0.jar /usr/share/java
-case-uco/java/dist/sleuthkit-caseuco-4.9.0.jar /usr/share/java
+bindings/java/dist/sleuthkit-4.10.0.jar /usr/share/java
+case-uco/java/dist/sleuthkit-caseuco-4.10.0.jar /usr/share/java
 
diff --git a/packages/sleuthkit.spec b/packages/sleuthkit.spec
index 4439db52aad80e20aa0f3b0238a2658e46d73294..ede52d5a7ae72ab81aa2c38b813e5f8b47b860da 100644
--- a/packages/sleuthkit.spec
+++ b/packages/sleuthkit.spec
@@ -1,5 +1,5 @@
 Name:		sleuthkit	
-Version:	4.9.0
+Version:	4.10.0
 Release:	1%{?dist}
 Summary:	The Sleuth Kit (TSK) is a library and collection of command line tools that allow you to investigate volume and file system data.	
 
diff --git a/tsk/Makefile.am b/tsk/Makefile.am
index a17d3de69df534508d971bf8548d96bb65ef9ede..a5dd8f2826cd1559a1cc33da0ba37b2958f88c63 100644
--- a/tsk/Makefile.am
+++ b/tsk/Makefile.am
@@ -8,6 +8,6 @@ libtsk_la_LIBADD = base/libtskbase.la img/libtskimg.la \
     vs/libtskvs.la fs/libtskfs.la hashdb/libtskhashdb.la \
     auto/libtskauto.la pool/libtskpool.la util/libtskutil.la
 # current:revision:age
-libtsk_la_LDFLAGS = -version-info 20:1:1 $(LIBTSK_LDFLAGS)
+libtsk_la_LDFLAGS = -version-info 20:2:1 $(LIBTSK_LDFLAGS)
 
 EXTRA_DIST = tsk_tools_i.h docs/Doxyfile docs/*.dox docs/*.html
diff --git a/tsk/base/tsk_base.h b/tsk/base/tsk_base.h
index 310ec6cc98e049b302861052c37cca7d6832ae59..a94cfa8e8d17a3360ec6237d0eded1e7a1f02a31 100644
--- a/tsk/base/tsk_base.h
+++ b/tsk/base/tsk_base.h
@@ -39,11 +39,11 @@
  * 3.1.2b1 would be 0x03010201.  Snapshot from Jan 2, 2003 would be
  * 0xFF030102.
  * See TSK_VERSION_STR for string form. */
-#define TSK_VERSION_NUM 0x040900ff
+#define TSK_VERSION_NUM 0x041000ff
 
 /** Version of code in string form. See TSK_VERSION_NUM for
  * integer form. */
-#define TSK_VERSION_STR "4.9.0"
+#define TSK_VERSION_STR "4.10.0"
 
 
 /* include the TSK-specific header file that we created in autoconf
diff --git a/tsk/docs/Doxyfile b/tsk/docs/Doxyfile
index cf6380f2c8e7cd4552e12a4e7360b5b61027b26f..ad5b9fb30045a24fc85abb03a06ed68c77d3cd8f 100644
--- a/tsk/docs/Doxyfile
+++ b/tsk/docs/Doxyfile
@@ -33,7 +33,7 @@ PROJECT_NAME           = "The Sleuth Kit"
 # if some version control system is used.
 
 # This is automatically updated  at release time. 
-PROJECT_NUMBER = 4.9.0
+PROJECT_NUMBER = 4.10.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer
@@ -883,7 +883,7 @@ GENERATE_HTML          = YES
 # put in front of it. If left blank `html' will be used as the default path.
 
 # NOTE: This is automatically updated at release time. 
-HTML_OUTPUT = api-docs/4.9.0/
+HTML_OUTPUT = api-docs/4.10.0/
 
 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for
 # each generated HTML page (for example: .htm,.php,.asp). If it is left blank