diff --git a/Core/build.xml b/Core/build.xml
index f32b5a28c3..a1290ef2e8 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -98,6 +98,8 @@
tofile="${ext.dir}/sleuthkit-${TSK_VERSION}.jar"/>
+ deletedTagList) {
+ eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
}
/**
@@ -1627,11 +1628,12 @@ public class Case {
*
* This should not be called from the event dispatch thread (EDT)
*
- * @param newTag new BlackboardArtifactTag added
- * @param removedTag The BlackboardArtifactTag that was removed.
+ * @param newTag The added ContentTag.
+ * @param removedTagList List of ContentTags that were removed as a result
+ * of the addition of newTag.
*/
- public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, BlackboardArtifactTag removedTag) {
- eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTag));
+ public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List removedTagList) {
+ eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
index 45068164c1..e9793a51b0 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* Event sent when a black board artifact tag is added.
*/
@Immutable
-public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent implements Serializable {
+public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent implements Serializable {
private static final long serialVersionUID = 1L;
@@ -38,8 +40,8 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent removedTagList) {
+ super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTagList != null ? getDeletedInfo(removedTagList) : null));
}
/**
@@ -54,4 +56,24 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent getDeletedInfo(List deletedTagList) {
+ List deletedInfoList = new ArrayList<>();
+ if (deletedTagList != null) {
+ for (BlackboardArtifactTag tag : deletedTagList) {
+ deletedInfoList.add(new DeletedBlackboardArtifactTagInfo(tag));
+ }
+ }
+
+ return deletedInfoList;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
index b3e55d3eac..10a80c772e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* An event that is fired when a ContentTag is added.
*/
@Immutable
-public class ContentTagAddedEvent extends TagAddedEvent implements Serializable {
+public class ContentTagAddedEvent extends TagAddedEvent implements Serializable {
private static final long serialVersionUID = 1L;
@@ -38,8 +40,8 @@ public class ContentTagAddedEvent extends TagAddedEvent implements S
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag);
}
- public ContentTagAddedEvent(ContentTag newTag, ContentTag deletedTag) {
- super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, (deletedTag != null ? new DeletedContentTagInfo(deletedTag) : null));
+ public ContentTagAddedEvent(ContentTag newTag, List deletedTagList) {
+ super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, getDeletedInfo(deletedTagList));
}
/**
@@ -50,7 +52,26 @@ public class ContentTagAddedEvent extends TagAddedEvent implements S
* @throws NoCurrentCaseException
* @throws TskCoreException
*/
+ @Override
ContentTag getTagByID() throws NoCurrentCaseException, TskCoreException {
return Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagByTagID(getTagID());
}
+
+ /**
+ * Create a list of DeletedContentTagInfo objects from a list of ContentTags.
+ *
+ * @param deletedTagList List of deleted ContentTags.
+ *
+ * @return List of DeletedContentTagInfo objects or empty list if deletedTagList was empty or null.
+ */
+ private static List getDeletedInfo(List deletedTagList) {
+ List deletedInfoList = new ArrayList<>();
+ if (deletedTagList != null) {
+ for (ContentTag tag : deletedTagList) {
+ deletedInfoList.add(new DeletedContentTagInfo(tag));
+ }
+ }
+
+ return deletedInfoList;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java
index ab737d7004..25a35a6d6c 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.TagDeletedEvent.DeletedTagInfo;
@@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
* Base Class for events that are fired when a Tag is added
*/
-abstract class TagAddedEvent extends AutopsyEvent implements Serializable {
+abstract class TagAddedEvent> extends AutopsyEvent implements Serializable {
private static final long serialVersionUID = 1L;
@@ -39,6 +41,8 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri
* re-loaded from the database in getNewValue()
*/
private transient T tag;
+
+ private List deletedTagInfoList;
/**
* The id of the tag that was added. This will be used to re-load the
@@ -50,10 +54,19 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri
this(propertyName, addedTag, null);
}
- TagAddedEvent(String propertyName, T addedTag, DeletedTagInfo deletedTagInfo) {
- super(propertyName, deletedTagInfo, null);
+ /**
+ * Construct a TagAddedEvent.
+ *
+ * @param propertyName Name of property changing
+ * @param addedTag Instance of added tag.
+ * @param deletedTagInfoList List of tags deleted as a result of the
+ * addition of addedTag.
+ */
+ TagAddedEvent(String propertyName, T addedTag, List deletedTagInfoList) {
+ super(propertyName, deletedTagInfoList, null);
tag = addedTag;
tagID = addedTag.getId();
+ this.deletedTagInfoList = deletedTagInfoList;
}
/**
@@ -73,7 +86,7 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri
public T getAddedTag() {
return getNewValue();
}
-
+
@Override
public T getNewValue() {
/**
@@ -95,6 +108,21 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri
return null;
}
}
+
+ /**
+ * Returns the list of tags that were removed as a result of the addition
+ * of the T.
+ *
+ * @return A list of removed tags or null if no tags were removed.
+ */
+ public List getDeletedTags() {
+ return deletedTagInfoList != null ? Collections.unmodifiableList(deletedTagInfoList) : null;
+ }
+
+ @Override
+ public Object getOldValue() {
+ return getDeletedTags();
+ }
/**
* implementors should override this to lookup the appropriate kind of tag
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java
index 2e70d407a0..0c65f3c58d 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java
@@ -80,7 +80,7 @@ final class TagNameDefinition implements Comparable {
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_TWO_NAME, new TagNameDefinition(CATEGORY_TWO_NAME, "", TagName.HTML_COLOR.LIME, TskData.FileKnown.BAD));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_THREE_NAME, new TagNameDefinition(CATEGORY_THREE_NAME, "", TagName.HTML_COLOR.YELLOW, TskData.FileKnown.BAD));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FOUR_NAME, new TagNameDefinition(CATEGORY_FOUR_NAME, "", TagName.HTML_COLOR.PURPLE, TskData.FileKnown.UNKNOWN));
- PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.SILVER, TskData.FileKnown.UNKNOWN));
+ PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.FUCHSIA, TskData.FileKnown.UNKNOWN));
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
index 5bdb2dc64e..49388d619e 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
@@ -481,7 +481,7 @@ public class TagsManager implements Closeable {
try {
Case currentCase = Case.getCurrentCaseThrows();
- currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
+ currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
} catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", ex);
@@ -701,7 +701,7 @@ public class TagsManager implements Closeable {
TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment);
try {
Case currentCase = Case.getCurrentCaseThrows();
- currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
+ currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
} catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", ex);
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoExaminer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoExaminer.java
new file mode 100644
index 0000000000..75fcceb68b
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoExaminer.java
@@ -0,0 +1,52 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+/**
+ * Encapsulates the concept of an examiner.
+ */
+final public class CentralRepoExaminer {
+
+ private final long id; // Row id in the examiners table in central repo database.
+ private final String loginName;
+
+ public CentralRepoExaminer(long id, String loginName) {
+ this.id = id;
+ this.loginName = loginName;
+ }
+
+ /**
+ * Returns the id.
+ *
+ * @return id
+ */
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * Returns the login name of examiner.
+ *
+ * @return login name
+ */
+ public String getLoginName() {
+ return this.loginName;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java
index 6a4a138cfb..b71606594f 100755
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.centralrepository.datamodel;
import java.sql.SQLException;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.sleuthkit.datamodel.TskData;
@@ -159,6 +160,17 @@ public interface CentralRepository {
* @param eamCase The case to update
*/
void updateCase(CorrelationCase eamCase) throws CentralRepoException;
+
+ /**
+ * Queries the examiner table for the given user name.
+ * Adds a row if the user is not found in the examiner table.
+ *
+ * @param examinerLoginName user name to look for.
+ * @return CentralRepoExaminer for the given user name.
+ * @throws CentralRepoException If there is an error in looking up or
+ * inserting the user in the examiners table.
+ */
+ CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException;
/**
* Retrieves Central Repo case based on an Autopsy Case
@@ -806,6 +818,24 @@ public interface CentralRepository {
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
+ /**
+ * Executes an INSERT sql statement on the central repository database.
+ * @param sql INSERT sql to execute.
+ *
+ * @throws CentralRepoException If there is an error.
+ */
+ void executeInsertSQL(String sql) throws CentralRepoException;
+
+ /**
+ * Executes a SELECT sql statement on the central repository database.
+ *
+ * @param sql SELECT sql to execute.
+ * @param queryCallback Query callback to handle the result of the query.
+ *
+ * @throws CentralRepoException If there is an error.
+ */
+ void executeSelectSQL(String sql, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException;
+
/**
* Get account type by type name.
*
@@ -815,6 +845,15 @@ public interface CentralRepository {
*/
CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException;
+ /**
+ * Gets all account types.
+ *
+ * @return Collection of all CR account types in the database.
+ *
+ * @throws CentralRepoException
+ */
+ Collection getAllAccountTypes() throws CentralRepoException;
+
/**
* Get an account from the accounts table matching the given type/ID.
* Inserts a row if one doesn't exists.
@@ -826,6 +865,5 @@ public interface CentralRepository {
* @throws CentralRepoException
*/
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
-
-
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryDbQueryCallback.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryDbQueryCallback.java
new file mode 100644
index 0000000000..ffc02b4a9f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryDbQueryCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * An interface to process the resultset from a Central Repository DB query.
+ * This enables clients of Central Repository to run custom queries and process
+ * the results themselves.
+ *
+ */
+interface CentralRepositoryDbQueryCallback {
+
+ /**
+ * Process the resultset from a query.
+ * @param rs ResultSet.
+ *
+ * @throws CentralRepoException In case of an error processing the result set.
+ * @throws SQLException In case of a SQL error in processing the result set.
+ */
+ void process(ResultSet rs) throws CentralRepoException, SQLException;
+}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java
index 4d5e2857a1..32121989e0 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java
@@ -77,7 +77,7 @@ public class CorrelationAttributeInstance implements Serializable {
) throws CentralRepoException, CorrelationAttributeNormalizationException {
this(type, value, -1, eamCase, eamDataSource, filePath, comment, knownStatus, fileObjectId, (long)-1);
}
- CorrelationAttributeInstance(
+ public CorrelationAttributeInstance(
Type type,
String value,
int instanceId,
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java
index 5fc458353b..fd4e26eca2 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java
@@ -18,13 +18,24 @@
*/
package org.sleuthkit.autopsy.centralrepository.datamodel;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.datamodel.SleuthkitCase;
+
/**
* This class abstracts a persona.
*
* An examiner may create a persona from an account.
*
*/
-class Persona {
+public class Persona {
/**
* Defines level of confidence in assigning a persona to an account.
@@ -50,9 +61,19 @@ class Persona {
return name;
}
- public int getLevel() {
+ public int getLevelId() {
return this.level_id;
}
+
+ static Confidence fromId(int value) {
+ for (Confidence confidence : Confidence.values()) {
+ if (confidence.getLevelId() == value) {
+ return confidence;
+ }
+ }
+ return Confidence.UNKNOWN;
+ }
+
}
/**
@@ -79,9 +100,559 @@ class Persona {
return description;
}
- public int getStatus() {
+ public int getStatusId() {
return this.status_id;
}
+
+ static PersonaStatus fromId(int value) {
+ for (PersonaStatus status : PersonaStatus.values()) {
+ if (status.getStatusId() == value) {
+ return status;
+ }
+ }
+ return PersonaStatus.UNKNOWN;
+ }
}
+ // Persona name to use if no name is specified.
+ private static final String DEFAULT_PERSONA_NAME = "Unknown";
+
+ // primary key in the Personas table in CR database
+ private final long id;
+ private final String uuidStr;
+ private final String name;
+ private final String comment;
+ private final long createdDate;
+ private final long modifiedDate;
+ private final PersonaStatus status;
+ private final CentralRepoExaminer examiner;
+
+ public long getId() {
+ return id;
+ }
+
+ public String getUuidStr() {
+ return uuidStr;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public long getCreatedDate() {
+ return createdDate;
+ }
+
+ public long getModifiedDate() {
+ return modifiedDate;
+ }
+
+ public PersonaStatus getStatus() {
+ return status;
+ }
+
+ public CentralRepoExaminer getExaminer() {
+ return examiner;
+ }
+
+ Persona(long id, String uuidStr, String name, String comment, long created_date, long modified_date, PersonaStatus status, CentralRepoExaminer examiner) {
+ this.id = id;
+ this.uuidStr = uuidStr;
+ this.name = name;
+ this.comment = comment;
+ this.createdDate = created_date;
+ this.modifiedDate = modified_date;
+ this.status = status;
+ this.examiner = examiner;
+ }
+
+ /**
+ * Creates a Persona and associates the specified account with it.
+ *
+ * @param personaName Persona name.
+ * @param comment Comment to associate with persona, may be null.
+ * @param status Persona status
+ * @param account Account for which the persona is being created.
+ * @param justification Justification for why this account belongs to this
+ * persona, may be null.
+ * @param confidence Confidence level for this association of Persona &
+ * account.
+ *
+ * @return PersonaAccount
+ * @throws CentralRepoException If there is an error creating the Persona.
+ */
+ public static PersonaAccount createPersonaForAccount(String personaName, String comment, PersonaStatus status, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
+ Persona persona = createPersona(personaName, comment, status);
+ return persona.addAccountToPersona(account, justification, confidence);
+ }
+
+ /**
+ * Inserts a row in the Persona tables.
+ *
+ * @param name Persona name, may be null - default name is used in that
+ * case.
+ * @param comment Comment to associate with persona, may be null.
+ * @param status Persona status.
+ *
+ * @return Persona corresponding to the row inserted in the personas table.
+ *
+ * @throws CentralRepoException If there is an error in adding a row to
+ * personas table.
+ */
+ private static Persona createPersona(String name, String comment, PersonaStatus status) throws CentralRepoException {
+ // generate a UUID for the persona
+ String uuidStr = UUID.randomUUID().toString();
+ CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
+
+ Instant instant = Instant.now();
+ Long timeStampMillis = instant.toEpochMilli();
+ String insertClause = " INTO personas (uuid, comment, name, created_date, modified_date, status_id, examiner_id ) "
+ + "VALUES ( '" + uuidStr + "', "
+ + "'" + ((StringUtils.isBlank(comment) ? "" : SleuthkitCase.escapeSingleQuotes(comment))) + "',"
+ + "'" + ((StringUtils.isBlank(name) ? DEFAULT_PERSONA_NAME : SleuthkitCase.escapeSingleQuotes(name))) + "',"
+ + timeStampMillis.toString() + ","
+ + timeStampMillis.toString() + ","
+ + status.getStatusId() + ","
+ + examiner.getId()
+ + ")";
+
+ CentralRepository.getInstance().executeInsertSQL(insertClause);
+ return getPersonaByUUID(uuidStr);
+ }
+
+ /**
+ * Associates an account with a persona by creating a row in the
+ * PersonaAccounts table.
+ *
+ * @param persona Persona to add the account to.
+ * @param account Account to add to persona.
+ * @param justification Reason for adding the account to persona, may be
+ * null.
+ * @param confidence Confidence level.
+ *
+ * @return PersonaAccount
+ * @throws CentralRepoException If there is an error.
+ */
+ public PersonaAccount addAccountToPersona(CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
+
+ CentralRepoExaminer currentExaminer = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
+
+ Instant instant = Instant.now();
+ Long timeStampMillis = instant.toEpochMilli();
+ String insertClause = " INTO persona_accounts (persona_id, account_id, justification, confidence_id, date_added, examiner_id ) "
+ + "VALUES ( "
+ + this.getId() + ", "
+ + account.getAccountId() + ", "
+ + "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
+ + confidence.getLevelId() + ", "
+ + timeStampMillis.toString() + ", "
+ + currentExaminer.getId()
+ + ")";
+
+ CentralRepository.getInstance().executeInsertSQL(insertClause);
+ return new PersonaAccount(this, account, justification, confidence, timeStampMillis, currentExaminer);
+ }
+
+ /**
+ * Callback to process a Persona query from the persona table.
+ */
+ private static class PersonaQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ private final Collection personaList = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet rs) throws SQLException {
+
+ while (rs.next()) {
+ CentralRepoExaminer examiner = new CentralRepoExaminer(
+ rs.getInt("examiner_id"),
+ rs.getString("login_name"));
+
+ PersonaStatus status = PersonaStatus.fromId(rs.getInt("status_id"));
+ Persona persona = new Persona(
+ rs.getInt("id"),
+ rs.getString("uuid"),
+ rs.getString("name"),
+ rs.getString("comment"),
+ Long.parseLong(rs.getString("created_date")),
+ Long.parseLong(rs.getString("modified_date")),
+ status,
+ examiner
+ );
+
+ personaList.add(persona);
+ }
+ }
+
+ Collection getPersonas() {
+ return Collections.unmodifiableCollection(personaList);
+ }
+ };
+
+ // Partial query string to select from personas table,
+ // just supply the where clause.
+ private static final String PERSONA_QUERY =
+ "SELECT p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name, e.display_name "
+ + "FROM personas as p "
+ + "INNER JOIN examiners as e ON e.id = p.examiner_id ";
+
+
+ /**
+ * Gets the row from the Personas table with the given UUID, creates and
+ * returns the Persona from that data.
+ *
+ * @param uuid Persona UUID to match.
+ * @return Persona matching the given UUID, may be null if no match is
+ * found.
+ *
+ * @throws CentralRepoException If there is an error in querying the
+ * Personas table.
+ */
+ private static Persona getPersonaByUUID(String uuid) throws CentralRepoException {
+
+ String queryClause =
+ PERSONA_QUERY
+ + "WHERE p.uuid = '" + uuid + "'";
+
+ PersonaQueryCallback queryCallback = new PersonaQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ Collection personas = queryCallback.getPersonas();
+
+ return personas.isEmpty() ? null : personas.iterator().next();
+ }
+
+ /**
+ * Gets the rows from the Personas table with matching name.
+ *
+ * @param partialName Name substring to match.
+ * @return Collection of personas matching the given name substring, may be
+ * empty if no match is found.
+ *
+ * @throws CentralRepoException If there is an error in querying the
+ * Personas table.
+ */
+ public static Collection getPersonaByName(String partialName) throws CentralRepoException {
+
+ String queryClause = PERSONA_QUERY
+ + "WHERE LOWER(p.name) LIKE " + "LOWER('%" + partialName + "%')" ;
+
+ PersonaQueryCallback queryCallback = new PersonaQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getPersonas();
+ }
+
+ /**
+ * Creates an alias for the Persona.
+ *
+ * @param alias Alias name.
+ * @param justification Reason for assigning the alias, may be null.
+ * @param confidence Confidence level.
+ *
+ * @return PersonaAlias
+ * @throws CentralRepoException If there is an error in creating the alias.
+ */
+ public PersonaAlias addAlias(String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
+ return PersonaAlias.addPersonaAlias(this, alias, justification, confidence);
+ }
+
+ /**
+ * Gets all aliases for the persona.
+ *
+ * @return A collection of aliases, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in retrieving aliases.
+ */
+ public Collection getAliases() throws CentralRepoException {
+ return PersonaAlias.getPersonaAliases(this.getId());
+ }
+
+ /**
+ * Adds specified metadata to the persona.
+ *
+ * @param name Metadata name.
+ * @param value Metadata value.
+ * @param justification Reason for adding the metadata, may be null.
+ * @param confidence Confidence level.
+ *
+ * @return PersonaMetadata
+ * @throws CentralRepoException If there is an error in adding metadata.
+ */
+ public PersonaMetadata addMetadata(String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
+ return PersonaMetadata.addPersonaMetadata(this.getId(), name, value, justification, confidence);
+ }
+
+ /**
+ * Gets all metadata for the persona.
+ *
+ * @return A collection of metadata, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in retrieving aliases.
+ */
+ public Collection getMetadata() throws CentralRepoException {
+ return PersonaMetadata.getPersonaMetadata(this.getId());
+ }
+
+ /**
+ * Gets all the Accounts for the Persona.
+ *
+ * @return Collection of PersonaAccounts, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in getting the
+ * persona_account.
+ */
+ public Collection getPersonaAccounts() throws CentralRepoException {
+ return PersonaAccount.getPersonaAccountsForPersona(this.getId());
+ }
+
+ /**
+ * Callback to process a query that gets cases for account instances of an
+ * account
+ */
+ private static class CaseForAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection correlationCases = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
+
+ while (resultSet.next()) {
+ // get Case for case_id
+ CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
+ correlationCases.add(correlationCase);
+ }
+ }
+
+ Collection getCases() {
+ return Collections.unmodifiableCollection(correlationCases);
+ }
+ };
+
+ /**
+ * Gets a list of cases that the persona appears in.
+ *
+ * @return Collection of cases that the persona appears in, may be empty.
+ * @throws CentralRepoException If there is an error in getting the cases
+ * from the database.
+ */
+ public Collection getCases() throws CentralRepoException {
+
+ Collection casesForPersona = new ArrayList<>();
+
+ // get all accounts for this persona
+ Collection accounts = PersonaAccount.getAccountsForPersona(this.getId());
+ for (CentralRepoAccount account : accounts) {
+ int corrTypeId = account.getAccountType().getCorrelationTypeId();
+ CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
+
+ String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
+ String querySql = "SELECT DISTINCT case_id FROM " + tableName
+ + " WHERE account_id = " + account.getAccountId();
+
+ CaseForAccountInstanceQueryCallback queryCallback = new CaseForAccountInstanceQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
+
+ // Add any cases that aren't already on the list.
+ for (CorrelationCase corrCase : queryCallback.getCases()) {
+ if (!casesForPersona.stream().anyMatch(p -> p.getCaseUUID().equalsIgnoreCase(corrCase.getCaseUUID()))) {
+ casesForPersona.add(corrCase);
+ }
+ }
+ }
+
+ return casesForPersona;
+ }
+
+ /**
+ * Callback to process a query that gets data source for account instances
+ * of an account
+ */
+ private static class DatasourceForAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection correlationDataSources = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
+
+ while (resultSet.next()) {
+ // get Case for case_id
+
+ CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
+ CorrelationDataSource correlationDatasource = CentralRepository.getInstance().getDataSourceById(correlationCase, resultSet.getInt("data_source_id"));
+
+ // Add data source to list if not already on it.
+ if (!correlationDataSources.stream().anyMatch(p -> Objects.equals(p.getDataSourceObjectID(), correlationDatasource.getDataSourceObjectID()))) {
+ correlationDataSources.add(correlationDatasource);
+ }
+ }
+ }
+
+ Collection getDataSources() {
+ return Collections.unmodifiableCollection(correlationDataSources);
+ }
+ };
+
+ /**
+ * Gets all data sources that the persona appears in.
+ *
+ * @return Collection of data sources that the persona appears in, may be
+ * empty.
+ *
+ * @throws CentralRepoException
+ */
+ public Collection getDataSources() throws CentralRepoException {
+ Collection correlationDataSources = new ArrayList<>();
+
+ Collection accounts = PersonaAccount.getAccountsForPersona(this.getId());
+ for (CentralRepoAccount account : accounts) {
+ int corrTypeId = account.getAccountType().getCorrelationTypeId();
+ CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
+
+ String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
+ String querySql = "SELECT case_id, data_source_id FROM " + tableName
+ + " WHERE account_id = " + account.getAccountId();
+
+ DatasourceForAccountInstanceQueryCallback queryCallback = new DatasourceForAccountInstanceQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
+
+ // Add any data sources that aren't already on the list.
+ for (CorrelationDataSource correlationDatasource : queryCallback.getDataSources()) {
+ if (!correlationDataSources.stream().anyMatch(p -> Objects.equals(p.getDataSourceObjectID(), correlationDatasource.getDataSourceObjectID()))) {
+ correlationDataSources.add(correlationDatasource);
+ }
+ }
+ }
+
+ return correlationDataSources;
+ }
+
+ /**
+ * Callback to process a query that gets Personas for a case/datasource.
+ */
+ private static class PersonaFromAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection personasList = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
+
+ while (resultSet.next()) {
+
+ // examiner that created the persona
+ CentralRepoExaminer personaExaminer = new CentralRepoExaminer(
+ resultSet.getInt("persona_examiner_id"),
+ resultSet.getString("persona_examiner_login_name"));
+
+ // create persona
+ PersonaStatus status = PersonaStatus.fromId(resultSet.getInt("status_id"));
+ Persona persona = new Persona(
+ resultSet.getInt("persona_id"),
+ resultSet.getString("uuid"),
+ resultSet.getString("name"),
+ resultSet.getString("comment"),
+ Long.parseLong(resultSet.getString("created_date")),
+ Long.parseLong(resultSet.getString("modified_date")),
+ status,
+ personaExaminer
+ );
+
+ personasList.add(persona);
+ }
+ }
+
+ Collection getPersonasList() {
+ return Collections.unmodifiableCollection(personasList);
+ }
+ };
+
+ /**
+ * Returns a query string for selecting personas for a case/datasource from
+ * the X_instance table for the given account type.
+ *
+ * @param crAccountType Account type to generate the query string for.
+ * @return Query substring.
+ * @throws CentralRepoException
+ */
+ private static String getPersonaFromInstanceTableQueryTemplate(CentralRepoAccount.CentralRepoAccountType crAccountType) throws CentralRepoException {
+
+ int corrTypeId = crAccountType.getCorrelationTypeId();
+ CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
+
+ String instanceTableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
+ return "SELECT " + instanceTableName + ".account_id, case_id, data_source_id, "
+ + " personas.id as persona_id, personas.uuid, personas.name, personas.comment, personas.created_date, personas.modified_date, personas.status_id, "
+ + " personas.examiner_id as persona_examiner_id, persona_examiner.login_name as persona_examiner_login_name, persona_examiner.display_name as persona_examiner_display_name "
+ + " FROM " + instanceTableName
+ + " JOIN persona_accounts as pa on pa.account_id = " + instanceTableName + ".account_id"
+ + " JOIN personas as personas on personas.id = pa.persona_id"
+ + " JOIN examiners as persona_examiner ON persona_examiner.id = personas.examiner_id ";
+
+ }
+
+ /**
+ * Get all the persona for a given case.
+ *
+ * @param correlationCase Case to look the persona in.
+ *
+ * @return Collection of personas, may be empty.
+ * @throws CentralRepoException
+ */
+ public static Collection getPersonasForCase(CorrelationCase correlationCase) throws CentralRepoException {
+ Collection personaList = new ArrayList<>();
+
+ Collection accountTypes = CentralRepository.getInstance().getAllAccountTypes();
+ for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
+
+ String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
+ + " WHERE case_id = " + correlationCase.getID();
+
+ PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
+
+ // Add persona that aren't already on the list.
+ for (Persona persona : queryCallback.getPersonasList()) {
+ if (!personaList.stream().anyMatch(p -> Objects.equals(p.getUuidStr(), persona.getUuidStr()))) {
+ personaList.add(persona);
+ }
+ }
+
+ }
+ return personaList;
+ }
+
+ /**
+ * Get all the persona for a given data source.
+ *
+ * @param CorrelationDataSource Data source to look the persona in.
+ *
+ * @return Collection of personas, may be empty.
+ * @throws CentralRepoException
+ */
+ public static Collection getPersonasForDataSource(CorrelationDataSource dataSource) throws CentralRepoException {
+ Collection personaList = new ArrayList<>();
+
+ Collection accountTypes = CentralRepository.getInstance().getAllAccountTypes();
+ for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
+
+ String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
+ + " WHERE data_source_id = " + dataSource.getID();
+
+ PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
+
+ // Add persona that aren't already on the list.
+ for (Persona persona : queryCallback.getPersonasList()) {
+ if (!personaList.stream().anyMatch(p -> Objects.equals(p.getUuidStr(), persona.getUuidStr()))) {
+ personaList.add(persona);
+ }
+ }
+
+ }
+ return personaList;
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java
new file mode 100644
index 0000000000..3b74b82aea
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java
@@ -0,0 +1,258 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * This class represents an association between a Persona and an Account.
+ *
+ * A Persona has at least one, possibly more, accounts associated with it.
+ *
+ *
+ */
+public class PersonaAccount {
+
+ private final Persona persona;
+ private final CentralRepoAccount account;
+ private final String justification;
+ private final Persona.Confidence confidence;
+ private final long dateAdded;
+ private final CentralRepoExaminer examiner;
+
+ public PersonaAccount(Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
+ this.persona = persona;
+ this.account = account;
+ this.justification = justification;
+ this.confidence = confidence;
+ this.dateAdded = dateAdded;
+ this.examiner = examiner;
+ }
+
+ public Persona getPersona() {
+ return persona;
+ }
+
+ public CentralRepoAccount getAccount() {
+ return account;
+ }
+
+ public String getJustification() {
+ return justification;
+ }
+
+ public Persona.Confidence getConfidence() {
+ return confidence;
+ }
+
+ public long getDateAdded() {
+ return dateAdded;
+ }
+
+ public CentralRepoExaminer getExaminer() {
+ return examiner;
+ }
+
+ /**
+ * Callback to process a Persona Accounts query.
+ */
+ private static class PersonaAccountsQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection personaAccountsList = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet rs) throws CentralRepoException, SQLException {
+
+ while (rs.next()) {
+ // examiner that created the persona/account association
+ CentralRepoExaminer paExaminer = new CentralRepoExaminer(
+ rs.getInt("pa_examiner_id"),
+ rs.getString("pa_examiner_login_name"));
+
+ // examiner that created the persona
+ CentralRepoExaminer personaExaminer = new CentralRepoExaminer(
+ rs.getInt("persona_examiner_id"),
+ rs.getString("persona_examiner_login_name"));
+
+ // create persona
+ Persona.PersonaStatus status = Persona.PersonaStatus.fromId(rs.getInt("status_id"));
+ Persona persona = new Persona(
+ rs.getInt("persona_id"),
+ rs.getString("uuid"),
+ rs.getString("name"),
+ rs.getString("comment"),
+ Long.parseLong(rs.getString("created_date")),
+ Long.parseLong(rs.getString("modified_date")),
+ status,
+ personaExaminer
+ );
+
+ // create account
+ CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
+ CentralRepoAccount account = new CentralRepoAccount(
+ rs.getInt("account_id"),
+ crAccountType,
+ rs.getString("account_unique_identifier"));
+
+ // create persona account
+ PersonaAccount personaAccount = new PersonaAccount(persona, account,
+ rs.getString("justification"),
+ Persona.Confidence.fromId(rs.getInt("confidence_id")),
+ Long.parseLong(rs.getString("date_added")),
+ paExaminer);
+
+ personaAccountsList.add(personaAccount);
+ }
+ }
+
+ Collection getPersonaAccountsList() {
+ return Collections.unmodifiableCollection(personaAccountsList);
+ }
+ };
+
+ // Query clause to select from persona_accounts table to create PersonaAccount(s)
+ private static final String PERSONA_ACCOUNTS_QUERY_CALUSE = "SELECT justification, confidence_id, date_added, persona_accounts.examiner_id as pa_examiner_id, pa_examiner.login_name as pa_examiner_login_name, pa_examiner.display_name as pa_examiner_display_name,"
+ + " personas.id as persona_id, personas.uuid, personas.name, personas.comment, personas.created_date, personas.modified_date, personas.status_id, "
+ + " personas.examiner_id as persona_examiner_id, persona_examiner.login_name as persona_examiner_login_name, persona_examiner.display_name as persona_examiner_display_name, "
+ + " accounts.id as account_id, account_type_id, account_unique_identifier,"
+ + " account_types.type_name as type_name "
+ + " FROM persona_accounts as persona_accounts "
+ + " JOIN personas as personas on persona_accounts.persona_id = personas.id "
+ + " JOIN accounts as accounts on persona_accounts.account_id = accounts.id "
+ + " JOIN account_types as account_types on accounts.account_type_id = account_types.id "
+ + " JOIN examiners as pa_examiner ON pa_examiner.id = persona_accounts.examiner_id "
+ + " JOIN examiners as persona_examiner ON persona_examiner.id = personas.examiner_id ";
+
+
+ /**
+ * Gets all the Accounts for the specified Persona.
+ *
+ * @param personaId Id of persona for which to get the accounts for.
+ * @return Collection of PersonaAccounts, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in getting the
+ * persona_account.
+ */
+ static Collection getPersonaAccountsForPersona(long personaId) throws CentralRepoException {
+ String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
+ + " WHERE persona_accounts.persona_id = " + personaId;
+
+ PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getPersonaAccountsList();
+ }
+
+ /**
+ * Gets all the Persona for the specified Account.
+ *
+ * @param accountId Id of account for which to get the Personas for.
+ * @return Collection of PersonaAccounts. may be empty.
+ *
+ * @throws CentralRepoException If there is an error in getting the
+ * persona_account.
+ */
+ public static Collection getPersonaAccountsForAccount(long accountId) throws CentralRepoException {
+ String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
+ + " WHERE persona_accounts.account_id = " + accountId;
+
+ PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getPersonaAccountsList();
+ }
+
+ /**
+ * Gets all the Persona associated with all the accounts matching the given
+ * account identifier substring.
+ *
+ * @param accountIdentifierSubstring Account identifier substring to search
+ * for.
+ * @return Collection of PersonaAccounts. may be empty.
+ *
+ * @throws CentralRepoException If there is an error in getting the
+ * persona_account.
+ */
+ public static Collection getPersonaAccountsForAccountIdentifier(String accountIdentifierSubstring) throws CentralRepoException {
+ String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
+ + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')";
+
+ PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getPersonaAccountsList();
+ }
+
+ /**
+ * Callback to process a query that gets all accounts belonging to a
+ * persona.
+ */
+ private static class AccountsForPersonaQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection accountsList = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet rs) throws CentralRepoException, SQLException {
+
+ while (rs.next()) {
+
+ // create account
+ CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
+ CentralRepoAccount account = new CentralRepoAccount(
+ rs.getInt("account_id"),
+ crAccountType,
+ rs.getString("account_unique_identifier"));
+
+ accountsList.add(account);
+ }
+ }
+
+ Collection getAccountsList() {
+ return Collections.unmodifiableCollection(accountsList);
+ }
+ };
+
+ /**
+ * Get all accounts associated with a persona.
+ *
+ * @param personaId Id of the persona to look for.
+ *
+ * @return Collection of all accounts associated with the given persona, may
+ * be empty.
+ * @throws CentralRepoException If there is an error in getting the accounts.
+ */
+ static Collection getAccountsForPersona(long personaId) throws CentralRepoException {
+ String queryClause = "SELECT account_id, "
+ + " accounts.account_type_id as account_type_id, accounts.account_unique_identifier as account_unique_identifier,"
+ + " account_types.type_name as type_name "
+ + " FROM persona_accounts "
+ + " JOIN accounts as accounts on persona_accounts.account_id = accounts.id "
+ + " JOIN account_types as account_types on accounts.account_type_id = account_types.id "
+ + " WHERE persona_accounts.persona_id = " + personaId;
+
+ AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getAccountsList();
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAlias.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAlias.java
new file mode 100644
index 0000000000..cdebd7097d
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAlias.java
@@ -0,0 +1,160 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.datamodel.SleuthkitCase;
+
+/**
+ * This class abstracts an alias assigned to a Persona.
+ * A Persona may have multiple aliases.
+ *
+ */
+public class PersonaAlias {
+
+ private final long personaId;
+ private final String alias;
+ private final String justification;
+ private final Persona.Confidence confidence;
+ private final long dateAdded;
+ private final CentralRepoExaminer examiner;
+
+ public long getPersonaId() {
+ return personaId;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public String getJustification() {
+ return justification;
+ }
+
+ public Persona.Confidence getConfidence() {
+ return confidence;
+ }
+
+ public long getDateAadded() {
+ return dateAdded;
+ }
+
+ public CentralRepoExaminer getExaminer() {
+ return examiner;
+ }
+
+ public PersonaAlias(long personaId, String alias, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
+ this.personaId = personaId;
+ this.alias = alias;
+ this.justification = justification;
+ this.confidence = confidence;
+ this.dateAdded = dateAdded;
+ this.examiner = examiner;
+ }
+
+ /**
+ * Creates an alias for the specified Persona.
+ *
+ * @param persona Persona for which the alias is being added.
+ * @param alias Alias name.
+ * @param justification Reason for assigning the alias, may be null.
+ * @param confidence Confidence level.
+ *
+ * @return PersonaAlias
+ * @throws CentralRepoException If there is an error in creating the alias.
+ */
+ static PersonaAlias addPersonaAlias(Persona persona, String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
+
+ CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
+
+ Instant instant = Instant.now();
+ Long timeStampMillis = instant.toEpochMilli();
+
+ String insertClause = " INTO persona_alias (persona_id, alias, justification, confidence_id, date_added, examiner_id ) "
+ + "VALUES ( "
+ + persona.getId() + ", "
+ + "'" + alias + "', "
+ + "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
+ + confidence.getLevelId() + ", "
+ + timeStampMillis.toString() + ", "
+ + examiner.getId()
+ + ")";
+
+ CentralRepository.getInstance().executeInsertSQL(insertClause);
+ return new PersonaAlias(persona.getId(), alias, justification, confidence, timeStampMillis, examiner);
+ }
+
+ /**
+ * Callback to process a Persona aliases query.
+ */
+ static class PersonaAliasesQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ private final Collection personaAliases = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet rs) throws SQLException {
+
+ while (rs.next()) {
+ CentralRepoExaminer examiner = new CentralRepoExaminer(
+ rs.getInt("examiner_id"),
+ rs.getString("login_name"));
+
+ PersonaAlias alias = new PersonaAlias(
+ rs.getLong("persona_id"),
+ rs.getString("alias"),
+ rs.getString("justification"),
+ Persona.Confidence.fromId(rs.getInt("confidence_id")),
+ Long.parseLong(rs.getString("date_added")),
+ examiner);
+
+ personaAliases.add(alias);
+ }
+ }
+
+ Collection getAliases() {
+ return Collections.unmodifiableCollection(personaAliases);
+ }
+ };
+
+ /**
+ * Gets all aliases for the persona with specified id.
+ *
+ * @param personaId Id of the persona for which to get the aliases.
+ * @return A collection of aliases, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in retrieving aliases.
+ */
+ public static Collection getPersonaAliases(long personaId) throws CentralRepoException {
+ String queryClause = "SELECT pa.id, pa.persona_id, pa.alias, pa.justification, pa.confidence_id, pa.date_added, pa.examiner_id, e.login_name, e.display_name "
+ + "FROM persona_alias as pa "
+ + "INNER JOIN examiners as e ON e.id = pa.examiner_id ";
+
+ PersonaAliasesQueryCallback queryCallback = new PersonaAliasesQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getAliases();
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaMetadata.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaMetadata.java
new file mode 100644
index 0000000000..713f00a981
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaMetadata.java
@@ -0,0 +1,172 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.datamodel.SleuthkitCase;
+
+/**
+ * This class abstracts metadata associated with a Persona.
+ * Metadata is in the form of a name/value pair.
+ *
+ * A Persona may have zero or more metadata.
+ *
+ */
+public class PersonaMetadata {
+
+ private final long personaId;
+ private final String name;
+ private final String value;
+ private final String justification;
+ private final Persona.Confidence confidence;
+ private final long dateAdded;
+ private final CentralRepoExaminer examiner;
+
+ public long getPersonaId() {
+ return personaId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getJustification() {
+ return justification;
+ }
+
+ public Persona.Confidence getConfidence() {
+ return confidence;
+ }
+
+ public long getDateAdded() {
+ return dateAdded;
+ }
+
+ public CentralRepoExaminer getExaminer() {
+ return examiner;
+ }
+
+ public PersonaMetadata(long personaId, String name, String value, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
+ this.personaId = personaId;
+ this.name = name;
+ this.value = value;
+ this.justification = justification;
+ this.confidence = confidence;
+ this.dateAdded = dateAdded;
+ this.examiner = examiner;
+ }
+
+ /**
+ * Adds specified metadata to the given persona.
+ *
+ * @param personaId Id of persona to add metadata for.
+ * @param name Metadata name.
+ * @param value Metadata value.
+ * @param justification Reason for adding the metadata, may be null.
+ * @param confidence Confidence level.
+ *
+ * @return PersonaMetadata
+ * @throws CentralRepoException If there is an error in adding metadata.
+ */
+ static PersonaMetadata addPersonaMetadata(long personaId, String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
+
+ CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
+
+ Instant instant = Instant.now();
+ Long timeStampMillis = instant.toEpochMilli();
+
+ String insertClause = " INTO persona_metadata (persona_id, name, value, justification, confidence_id, date_added, examiner_id ) "
+ + "VALUES ( "
+ + personaId + ", "
+ + "'" + name + "', "
+ + "'" + value + "', "
+ + "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
+ + confidence.getLevelId() + ", "
+ + timeStampMillis.toString() + ", "
+ + examiner.getId()
+ + ")";
+
+ CentralRepository.getInstance().executeInsertSQL(insertClause);
+ return new PersonaMetadata(personaId, name, value, justification, confidence, timeStampMillis, examiner);
+ }
+
+ /**
+ * Callback to process a Persona metadata query.
+ */
+ private static class PersonaMetadataQueryCallback implements CentralRepositoryDbQueryCallback {
+
+ Collection personaMetadataList = new ArrayList<>();
+
+ @Override
+ public void process(ResultSet rs) throws SQLException {
+
+ while (rs.next()) {
+ CentralRepoExaminer examiner = new CentralRepoExaminer(
+ rs.getInt("examiner_id"),
+ rs.getString("login_name"));
+
+ PersonaMetadata metaData = new PersonaMetadata(
+ rs.getLong("persona_id"),
+ rs.getString("name"),
+ rs.getString("value"),
+ rs.getString("justification"),
+ Persona.Confidence.fromId(rs.getInt("confidence_id")),
+ Long.parseLong(rs.getString("date_added")),
+ examiner);
+
+ personaMetadataList.add(metaData);
+ }
+ }
+
+ Collection getMetadataList() {
+ return Collections.unmodifiableCollection(personaMetadataList);
+ }
+ };
+
+ /**
+ * Gets all metadata for the persona with specified id.
+ *
+ * @param personaId Id of the persona for which to get the metadata.
+ * @return A collection of metadata, may be empty.
+ *
+ * @throws CentralRepoException If there is an error in retrieving aliases.
+ */
+ static Collection getPersonaMetadata(long personaId) throws CentralRepoException {
+ String queryClause = "SELECT pmd.id, pmd.persona_id, pmd.name, pmd.value, pmd.justification, pmd.confidence_id, pmd.date_added, pmd.examiner_id, e.login_name, e.display_name "
+ + "FROM persona_metadata as pmd "
+ + "INNER JOIN examiners as e ON e.id = pmd.examiner_id ";
+
+ PersonaMetadataQueryCallback queryCallback = new PersonaMetadataQueryCallback();
+ CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
+
+ return queryCallback.getMetadataList();
+
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java
index 24d02ae2c2..de88396de7 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java
@@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.healthmonitor.HealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
+import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskData;
/**
@@ -101,6 +102,9 @@ abstract class RdbmsCentralRepo implements CentralRepository {
// Update Test code if this changes. It's hard coded there.
static final int DEFAULT_BULK_THRESHHOLD = 1000;
+ private static final int QUERY_STR_MAX_LEN = 1000;
+
+
/**
* Connect to the DB and initialize it.
*
@@ -216,7 +220,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
/**
* Reset the contents of the caches associated with EamDb results.
*/
- protected final void clearCaches() {
+ public final void clearCaches() {
synchronized(typeCache) {
typeCache.invalidateAll();
isCRTypeCacheInitialized = false;
@@ -225,6 +229,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
caseCacheById.invalidateAll();
dataSourceCacheByDsObjectId.invalidateAll();
dataSourceCacheById.invalidateAll();
+ accountsCache.invalidateAll();
}
/**
@@ -1111,6 +1116,31 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
+
+ @Override
+ public Collection getAllAccountTypes() throws CentralRepoException {
+
+ Collection accountTypes = new ArrayList<>();
+
+ String sql = "SELECT * FROM account_types";
+ try ( Connection conn = connect();
+ PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
+
+
+ try (ResultSet resultSet = preparedStatement.executeQuery();) {
+ while (resultSet.next()) {
+ Account.Type acctType = new Account.Type(resultSet.getString("type_name"), resultSet.getString("display_name"));
+ CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id"));
+
+ accountTypes.add(crAccountType);
+ }
+ }
+ } catch (SQLException ex) {
+ throw new CentralRepoException("Error getting account types from central repository.", ex); // NON-NLS
+ }
+ return accountTypes;
+ }
+
/**
* Gets the CR account type for the specified type name.
*
@@ -2486,6 +2516,48 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
}
+ @Override
+ public void executeInsertSQL(String insertClause) throws CentralRepoException {
+
+ if (insertClause == null) {
+ throw new CentralRepoException("Insert SQL is null");
+ }
+
+ String sql = getPlatformSpecificInsertSQL(insertClause);
+ try (Connection conn = connect();
+ PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
+ preparedStatement.executeUpdate();
+ } catch (SQLException ex) {
+ throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", sql, ex.getMessage()), ex);
+ }
+ }
+
+ @Override
+ public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
+ if (queryCallback == null) {
+ throw new CentralRepoException("Query callback is null");
+ }
+
+ if (selectSQL == null) {
+ throw new CentralRepoException("Select SQL is null");
+ }
+
+ StringBuilder sqlSb = new StringBuilder(QUERY_STR_MAX_LEN);
+ if (selectSQL.trim().toUpperCase().startsWith("SELECT") == false) {
+ sqlSb.append("SELECT ");
+ }
+
+ sqlSb.append(selectSQL);
+
+ try (Connection conn = connect();
+ PreparedStatement preparedStatement = conn.prepareStatement(sqlSb.toString());
+ ResultSet resultSet = preparedStatement.executeQuery();) {
+ queryCallback.process(resultSet);
+ } catch (SQLException ex) {
+ throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", selectSQL, ex.getMessage()), ex);
+ }
+ }
+
@Override
public CentralRepoOrganization newOrganization(CentralRepoOrganization eamOrg) throws CentralRepoException {
if (eamOrg == null) {
@@ -2624,6 +2696,61 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
}
+ /**
+ * Queries the examiner table for the given user name.
+ * Adds a row if the user is not found in the examiner table.
+ *
+ * @param examinerLoginName user name to look for.
+ * @return CentralRepoExaminer for the given user name.
+ * @throws CentralRepoException If there is an error in looking up or
+ * inserting the user in the examiners table.
+ */
+
+ @Override
+ public CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException {
+
+ String querySQL = "SELECT * FROM examiners WHERE login_name = '" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "'";
+ try (Connection connection = connect();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(querySQL);) {
+
+ if (resultSet.next()) {
+ return new CentralRepoExaminer(resultSet.getLong("id"), resultSet.getString("login_name"));
+ } else {
+ // Could not find this user in the Examiner table, add a row for it.
+ try {
+ String insertSQL;
+ switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) {
+ case POSTGRESQL:
+ insertSQL = "INSERT INTO examiners (login_name) VALUES ('" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "')" + getConflictClause(); //NON-NLS
+ break;
+ case SQLITE:
+ insertSQL = "INSERT OR IGNORE INTO examiners (login_name) VALUES ('" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "')"; //NON-NLS
+ break;
+ default:
+ throw new CentralRepoException(String.format("Cannot add examiner to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS
+ }
+ statement.execute(insertSQL);
+
+ // Query the table again to get the row for the user
+ try (ResultSet resultSet2 = statement.executeQuery(querySQL)) {
+ if (resultSet2.next()) {
+ return new CentralRepoExaminer(resultSet2.getLong("id"), resultSet2.getString("login_name"));
+ } else {
+ throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName);
+ }
+ }
+
+ } catch (SQLException ex) {
+ throw new CentralRepoException("Error inserting row in examiners", ex);
+ }
+ }
+
+ } catch (SQLException ex) {
+ throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName, ex);
+ }
+ }
+
/**
* Update an existing organization.
*
@@ -3489,6 +3616,19 @@ abstract class RdbmsCentralRepo implements CentralRepository {
);
}
+ private String getPlatformSpecificInsertSQL(String sql) throws CentralRepoException {
+
+ switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) {
+ case POSTGRESQL:
+ return "INSERT " + sql + " ON CONFLICT DO NOTHING"; //NON-NLS
+ case SQLITE:
+ return "INSERT OR IGNORE " + sql;
+
+ default:
+ throw new CentralRepoException("Unknown Central Repo DB platform" + CentralRepoDbManager.getSavedDbChoice().getDbPlatform());
+ }
+ }
+
/**
* Determine if a specific column already exists in a specific table
*
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java
index aedfce276d..2a157549f3 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java
@@ -811,14 +811,14 @@ public class RdbmsCentralRepoFactory {
try (Statement stmt = conn.createStatement()) {
// populate the confidence table
for (Confidence confidence : Persona.Confidence.values()) {
- String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevel() + ", '" + confidence.toString() + "')" //NON-NLS
+ String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevelId() + ", '" + confidence.toString() + "')" //NON-NLS
+ getOnConflictDoNothingClause(selectedPlatform);
stmt.execute(sqlString);
}
// populate the persona_status table
for (PersonaStatus status : Persona.PersonaStatus.values()) {
- String sqlString = "INSERT INTO persona_status (status_id, status) VALUES ( " + status.getStatus() + ", '" + status.toString() + "')" //NON-NLS
+ String sqlString = "INSERT INTO persona_status (status_id, status) VALUES ( " + status.getStatusId() + ", '" + status.toString() + "')" //NON-NLS
+ getOnConflictDoNothingClause(selectedPlatform);
stmt.execute(sqlString);
}
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java
index e5aecdf788..80f1df746c 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java
@@ -833,6 +833,27 @@ final class SqliteCentralRepo extends RdbmsCentralRepo {
}
}
+ @Override
+ public void executeInsertSQL(String insertSQL) throws CentralRepoException {
+ try {
+ acquireSharedLock();
+ super.executeInsertSQL(insertSQL);
+ } finally {
+ releaseSharedLock();
+ }
+ }
+
+ @Override
+ public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
+ try {
+ acquireSharedLock();
+ super.executeSelectSQL(selectSQL, queryCallback);
+ } finally {
+ releaseSharedLock();
+ }
+ }
+
+
/**
* Check whether a reference set with the given name/version is in the
* central repo. Used to check for name collisions when creating reference
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
index 843e476499..400eeeb033 100644
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
@@ -37,6 +37,7 @@ import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
@@ -62,6 +63,11 @@ import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
+import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
+import org.sleuthkit.datamodel.Account;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT;
+import org.sleuthkit.datamodel.CommunicationsUtils;
/**
* Listen for ingest events and update entries in the Central Repository
@@ -337,6 +343,94 @@ public class IngestEventsListener {
event = evt;
}
+ /**
+ * Automatically creates personas from all the TSK_CONTACT artifacts
+ * found in a data source.
+ *
+ * @param dataSource Data source that was just analyzed.
+ * @throws TskCoreException If there is any error getting contact
+ * artifacts from case database.
+ * @throws CentralRepoException If there is an error in creating
+ * personas in the Central Repo.
+ */
+ private void autoGenerateContactPersonas(Content dataSource) throws TskCoreException, CentralRepoException {
+
+ Blackboard blackboard;
+ try {
+ blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
+ } catch (NoCurrentCaseException ex) {
+ LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
+ return;
+ }
+
+ // get all TSK_CONTACT artifacts in this data source.
+ List contactArtifacts = blackboard.getArtifacts(TSK_CONTACT.getTypeID(), dataSource.getId());
+ for (BlackboardArtifact artifact : contactArtifacts) {
+
+ BlackboardAttribute nameAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
+ String personaName = (nameAttr != null) ? nameAttr.getValueString() : null;
+
+ // Get phone number and email attributes.
+ BlackboardAttribute phoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
+ BlackboardAttribute homePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME));
+ BlackboardAttribute mobilePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE));
+ BlackboardAttribute emailAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
+
+ Persona persona = personaFromContactAttribute(null, Account.Type.PHONE, phoneAttr, personaName);
+ persona = personaFromContactAttribute(persona, Account.Type.PHONE, homePhoneAttr, personaName);
+ persona = personaFromContactAttribute(persona, Account.Type.PHONE, mobilePhoneAttr, personaName);
+ personaFromContactAttribute(persona, Account.Type.EMAIL, emailAttr, personaName);
+ }
+ }
+
+
+
+ /**
+ * Gets central repo account for the given attribute for a TSK_CONTACT
+ * artifact. Associates the given persona with that account. Creates a
+ * Persona, if one isn't provided.
+ *
+ * @param persona Persona to associate with the account. May be null, in
+ * which case a persona is created first.
+ * @param accountType Account type of account to be associated.
+ * @param attribute Attribute form which get the account id.
+ * @param personaName Persona name, if a persona needs to be created.
+ * @return Persona created or associated with the account.
+ *
+ * @throws TskCoreException If there is an error in normalizing the
+ * account id.
+ * @throws CentralRepoException If there is an erorr is getting the
+ * account or associating the persona with it.
+ */
+ private Persona personaFromContactAttribute(Persona persona, Account.Type accountType, BlackboardAttribute attribute, String personaName) throws CentralRepoException, TskCoreException {
+
+ Persona personaToReturn = persona;
+ if (attribute != null) {
+
+ String accountId = attribute.getValueString();
+ if (CommunicationsUtils.isValidAccountId(accountType, accountId)) {
+ if (accountType == Account.Type.PHONE) {
+ accountId = CommunicationsUtils.normalizePhoneNum(accountId);
+ } else if (accountType == Account.Type.EMAIL) {
+ accountId = CommunicationsUtils.normalizeEmailAddress(accountId);
+ }
+
+ CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountType.getTypeName());
+ CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountId);
+
+ PersonaAccount personaAccount;
+ // If persona doesnt exist, create one
+ if (persona == null) {
+ personaAccount = Persona.createPersonaForAccount(personaName, "Auto generated contact persona", Persona.PersonaStatus.UNKNOWN, crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
+ personaToReturn = personaAccount.getPersona();
+ } else {
+ persona.addAccountToPersona(crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
+ }
+ }
+ }
+ return personaToReturn;
+ }
+
@Override
public void run() {
// clear the tracker to reduce memory usage
@@ -411,6 +505,8 @@ public class IngestEventsListener {
correlationDataSource.setSha256(imageSha256Hash);
}
}
+ // automatically generate persona from contact artifacts.
+ autoGenerateContactPersonas(dataSource);
}
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, String.format(
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED
index c8cebe69fc..4d4d3cf10f 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED
@@ -17,7 +17,10 @@ ContextViewer.messageFrom=From
ContextViewer.messageOn=On
ContextViewer.messageTo=To
ContextViewer.on=Opened at
+ContextViewer.programExecution=Program Execution:
ContextViewer.recentDocs=Recent Documents:
+ContextViewer.runOn=Program Run On
+ContextViewer.runUnknown=\ Program Run at unknown time
ContextViewer.title=Context
ContextViewer.toolTip=Displays context for selected file.
ContextViewer.unknown=Opened at unknown time
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
index 7200f6747f..f7f1f6559e 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
@@ -334,7 +334,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
@NbBundle.Messages({
"ContextViewer.attachmentSource=Attached to: ",
"ContextViewer.downloadSource=Downloaded from: ",
- "ContextViewer.recentDocs=Recent Documents: "
+ "ContextViewer.recentDocs=Recent Documents: ",
+ "ContextViewer.programExecution=Program Execution: "
})
private void setSourceFields(BlackboardArtifact associatedArtifact) throws TskCoreException {
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
@@ -357,6 +358,11 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
contextUsagePanels.add(usagePanel);
+ } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
+ String sourceName = Bundle.ContextViewer_programExecution();
+ String sourceText = programExecArtifactToString(associatedArtifact);
+ javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
+ contextUsagePanels.add(usagePanel);
}
}
@@ -416,6 +422,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
return sb.toString();
}
+ /**
+ * Returns a display string with Program Execution
+ * artifact.
+ *
+ * @param artifact artifact to get doc from.
+ *
+ * @return Display string with download URL and date/time.
+ *
+ * @throws TskCoreException
+ */
+ @NbBundle.Messages({
+ "ContextViewer.runOn=Program Run On",
+ "ContextViewer.runUnknown= Program Run at unknown time"
+ })
+ private String programExecArtifactToString(BlackboardArtifact artifact) throws TskCoreException {
+ StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN);
+ Map attributesMap = getAttributesMap(artifact);
+
+ BlackboardAttribute attribute = attributesMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
+
+ if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == artifact.getArtifactTypeID()) {
+ if (attribute != null && attribute.getValueLong() > 0) {
+ appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_runOn());
+ } else {
+ sb.append(Bundle.ContextViewer_runUnknown());
+ }
+ }
+ return sb.toString();
+ }
+
/**
* Returns a abbreviated display string for a message artifact.
*
diff --git a/Core/src/org/sleuthkit/autopsy/events/AutopsyEventPublisher.java b/Core/src/org/sleuthkit/autopsy/events/AutopsyEventPublisher.java
index 02a5d2e2ab..f3fb273bcf 100644
--- a/Core/src/org/sleuthkit/autopsy/events/AutopsyEventPublisher.java
+++ b/Core/src/org/sleuthkit/autopsy/events/AutopsyEventPublisher.java
@@ -42,9 +42,9 @@ public final class AutopsyEventPublisher {
private static final Logger logger = Logger.getLogger(AutopsyEventPublisher.class.getName());
private static final int MAX_REMOTE_EVENT_PUBLISH_TRIES = 1;
private final LocalEventPublisher localPublisher; // LocalEventPublisher is thread-safe
- @GuardedBy("this)")
+ @GuardedBy("this")
private RemoteEventPublisher remotePublisher;
- @GuardedBy("this)")
+ @GuardedBy("this")
private String currentChannelName;
/**
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java
new file mode 100644
index 0000000000..2b1d72534e
--- /dev/null
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java
@@ -0,0 +1,904 @@
+/*
+ * Central Repository
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.centralrepository.datamodel;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.Collection;
+import junit.framework.Assert;
+import static junit.framework.Assert.assertTrue;
+import junit.framework.TestCase;
+import junit.framework.Test;
+import org.apache.commons.io.FileUtils;
+
+import org.netbeans.junit.NbModuleSuite;
+import org.openide.util.Exceptions;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.TskData;
+
+
+/**
+ *
+ * Tests the Persona API in CentralRepository.
+ */
+public class CentralRepoPersonasTest extends TestCase {
+
+ private final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest");
+
+
+ private static final String CASE_1_UUID = "case1_uuid";
+ private static final String CASE_2_UUID = "case2_uuid";
+ private static final String CASE_3_UUID = "case3_uuid";
+ private static final String CASE_4_UUID = "case4_uuid";
+
+ private static final String DS1_DEVICEID = "dataSource1_deviceID";
+ private static final String DS2_DEVICEID = "dataSource2_deviceID";
+ private static final String DS3_DEVICEID = "dataSource3_deviceID";
+ private static final String DS4_DEVICEID = "dataSource4_deviceID";
+ private static final String DS5_DEVICEID = "dataSource5_deviceID";
+ private static final String DS6_DEVICEID = "dataSource6_deviceID";
+
+
+ private static final long CASE_1_DATA_SOURCE_1_ID = 11;
+ private static final long CASE_1_DATA_SOURCE_2_ID = 12;
+ private static final long CASE_2_DATA_SOURCE_1_ID = 21;
+
+ private static final long CASE_3_DATA_SOURCE_1_ID = 31;
+ private static final long CASE_3_DATA_SOURCE_2_ID = 32;
+
+ private static final long CASE_4_DATA_SOURCE_1_ID = 41;
+
+
+ private static final String PHONE_NUM_1 = "+1 441-231-2552";
+
+
+ private static final String FACEBOOK_ID_CATDOG = "BalooSherkhan";
+
+ private static final String DOG_EMAIL_ID = "superpupper@junglebook.com";
+ private static final String CAT_WHATSAPP_ID = "111 222 3333";
+ private static final String EMAIL_ID_1 = "rkipling@jungle.book";
+
+ private static final String HOLMES_SKYPE_ID = "live:holmes@221baker.com";
+
+
+ private static final String DOG_PERSONA_NAME = "Baloo McDog";
+ private static final String CAT_PERSONA_NAME = "SherKhan";
+ private static final String HOLMES_PERSONA_NAME = "Sherlock Holmes";
+
+
+ private CorrelationCase case1;
+ private CorrelationCase case2;
+ private CorrelationCase case3;
+ private CorrelationCase case4;
+
+ private CorrelationDataSource dataSource1fromCase1;
+ private CorrelationDataSource dataSource2fromCase1;
+ private CorrelationDataSource dataSource1fromCase2;
+
+ private CorrelationDataSource dataSource1fromCase3;
+ private CorrelationDataSource dataSource2fromCase3;
+ private CorrelationDataSource dataSource1fromCase4;
+
+ private CentralRepoOrganization org1;
+ private CentralRepoOrganization org2;
+
+ private CentralRepoAccount.CentralRepoAccountType phoneAccountType;
+ private CentralRepoAccount.CentralRepoAccountType emailAccountType;
+ private CentralRepoAccount.CentralRepoAccountType facebookAccountType;
+ private CentralRepoAccount.CentralRepoAccountType textnowAccountType;
+ private CentralRepoAccount.CentralRepoAccountType whatsAppAccountType;
+ private CentralRepoAccount.CentralRepoAccountType skypeAccountType;
+
+
+ private CorrelationAttributeInstance.Type phoneInstanceType;
+ private CorrelationAttributeInstance.Type emailInstanceType;
+ private CorrelationAttributeInstance.Type facebookInstanceType;
+ private CorrelationAttributeInstance.Type textnowInstanceType;
+ private CorrelationAttributeInstance.Type whatsAppInstanceType;
+ private CorrelationAttributeInstance.Type skypeInstanceType;
+
+
+ // NbModuleSuite requires these tests use Junit 3.8
+ // Extension of the TestCase class is how tests were defined and used
+ // in Junit 3.8
+ public static Test suite() {
+ NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CentralRepoPersonasTest.class).
+ clusters(".*").
+ enableModules(".*");
+ return conf.suite();
+ }
+
+ // This function is run before every test, NOT before the entire collection of
+ // tests defined in this class are run.
+ @Override
+ public void setUp() throws CentralRepoException, IOException {
+ // Tear down the previous run, if need be.
+ if (Files.exists(testDirectory)) {
+ tearDown();
+ }
+
+ // Create the test directory
+ Files.createDirectory(testDirectory);
+
+ final String CR_DB_NAME = "testcentralrepo.db";
+
+
+
+ SqliteCentralRepoSettings sqliteSettings = new SqliteCentralRepoSettings();
+ sqliteSettings.setDbName(CR_DB_NAME);
+ sqliteSettings.setDbDirectory(testDirectory.toString());
+
+ if (!sqliteSettings.dbDirectoryExists() && !sqliteSettings.createDbDirectory()) {
+ Assert.fail("Failed to create central repo directory.");
+ }
+
+ RdbmsCentralRepoFactory factory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, sqliteSettings);
+ if (!factory.initializeDatabaseSchema() || !factory.insertDefaultDatabaseContent()) {
+ Assert.fail("Failed to initialize central repo database");
+ }
+
+ sqliteSettings.saveSettings();
+ CentralRepoDbUtil.setUseCentralRepo(true);
+ CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE);
+
+ Path crDbFilePath = Paths.get(testDirectory.toString(), CR_DB_NAME);
+ if (!Files.exists(crDbFilePath)) {
+ Assert.fail("Failed to create central repo database, should be located at + " + crDbFilePath);
+ }
+
+
+ // Set up some default objects to be used by the tests
+ try {
+ case1 = new CorrelationCase(CASE_1_UUID, "case1");
+ case1 = CentralRepository.getInstance().newCase(case1);
+ assertTrue("Failed to create test object case1", case1 != null);
+
+ case2 = new CorrelationCase(CASE_2_UUID, "case2");
+ case2 = CentralRepository.getInstance().newCase(case2);
+ assertTrue("Failed to create test object case2", case2 != null);
+
+ case3 = new CorrelationCase(CASE_3_UUID, "case3");
+ case3 = CentralRepository.getInstance().newCase(case3);
+ assertTrue("Failed to create test object case3", case3 != null);
+
+ case4 = new CorrelationCase(CASE_4_UUID, "case4");
+ case4 = CentralRepository.getInstance().newCase(case4);
+ assertTrue("Failed to create test object case4", case4 != null);
+
+ dataSource1fromCase1 = new CorrelationDataSource(case1, DS1_DEVICEID, "dataSource1", CASE_1_DATA_SOURCE_1_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource1fromCase1);
+ dataSource1fromCase1 = CentralRepository.getInstance().getDataSource(case1, dataSource1fromCase1.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource1fromCase1", dataSource1fromCase1 != null);
+
+ dataSource2fromCase1 = new CorrelationDataSource(case1, DS2_DEVICEID, "dataSource2", CASE_1_DATA_SOURCE_2_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource2fromCase1);
+ dataSource2fromCase1 = CentralRepository.getInstance().getDataSource(case1, dataSource2fromCase1.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null);
+
+ dataSource1fromCase2 = new CorrelationDataSource(case2, DS3_DEVICEID, "dataSource3", CASE_2_DATA_SOURCE_1_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource1fromCase2);
+ dataSource1fromCase2 = CentralRepository.getInstance().getDataSource(case2, dataSource1fromCase2.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource1fromCase2", dataSource1fromCase2 != null);
+
+ dataSource1fromCase3 = new CorrelationDataSource(case3, DS4_DEVICEID, "dataSource4", CASE_3_DATA_SOURCE_1_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource1fromCase3);
+ dataSource1fromCase3 = CentralRepository.getInstance().getDataSource(case3, dataSource1fromCase3.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource1fromCase3", dataSource1fromCase3 != null);
+
+ dataSource2fromCase3 = new CorrelationDataSource(case3, DS5_DEVICEID, "dataSource5", CASE_3_DATA_SOURCE_2_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource2fromCase3);
+ dataSource2fromCase3 = CentralRepository.getInstance().getDataSource(case3, dataSource2fromCase3.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource2fromCase3", dataSource2fromCase3 != null);
+
+ dataSource1fromCase4 = new CorrelationDataSource(case4, DS6_DEVICEID, "dataSource6", CASE_4_DATA_SOURCE_1_ID, null, null, null);
+ CentralRepository.getInstance().newDataSource(dataSource1fromCase4);
+ dataSource1fromCase4 = CentralRepository.getInstance().getDataSource(case4, dataSource1fromCase4.getDataSourceObjectID());
+ assertTrue("Failed to create test object dataSource1fromCase4", dataSource1fromCase4 != null);
+
+ org1 = new CentralRepoOrganization("org1");
+ org1 = CentralRepository.getInstance().newOrganization(org1);
+
+ org2 = new CentralRepoOrganization("org2");
+ org2 = CentralRepository.getInstance().newOrganization(org2);
+
+ // get some correltion types for different account types, for later use
+ phoneAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.PHONE.getTypeName());
+ phoneInstanceType = CentralRepository.getInstance().getCorrelationTypeById(phoneAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(PHONE) returned null", phoneInstanceType != null);
+
+ emailAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.EMAIL.getTypeName());
+ emailInstanceType = CentralRepository.getInstance().getCorrelationTypeById(emailAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(EMAIL) returned null", emailInstanceType != null);
+
+ facebookAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.FACEBOOK.getTypeName());
+ facebookInstanceType = CentralRepository.getInstance().getCorrelationTypeById(facebookAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(FACEBOOK) returned null", facebookInstanceType != null);
+
+ textnowAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.TEXTNOW.getTypeName());
+ textnowInstanceType = CentralRepository.getInstance().getCorrelationTypeById(textnowAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(TEXTNOW) returned null", textnowInstanceType != null);
+
+ whatsAppAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.WHATSAPP.getTypeName());
+ whatsAppInstanceType = CentralRepository.getInstance().getCorrelationTypeById(whatsAppAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(WHATSAPP) returned null", whatsAppInstanceType != null);
+
+ skypeAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.SKYPE.getTypeName());
+ skypeInstanceType = CentralRepository.getInstance().getCorrelationTypeById(skypeAccountType.getCorrelationTypeId());
+ assertTrue("getCorrelationTypeById(SKYPE) returned null", skypeInstanceType != null);
+
+
+ } catch (CentralRepoException ex) {
+ Exceptions.printStackTrace(ex);
+ Assert.fail(ex.getMessage());
+ }
+
+ }
+
+ // This function is run after every test, NOT after the entire collection of
+ // tests defined in the class are run.
+ @Override
+ public void tearDown() throws CentralRepoException, IOException {
+ // Close and delete the test case and central repo db
+ if (CentralRepository.isEnabled()) {
+ CentralRepository.getInstance().shutdownConnections();
+ }
+ FileUtils.deleteDirectory(testDirectory.toFile());
+ }
+
+
+ /**
+ * Basic tests for:
+ * - Persona creation,
+ * - adding aliases and metadata
+ * - add additional accounts to Persona
+ * - Get Persona(s) by account
+ * - get Account(s) by Persona
+ *
+ */
+ public void testBasicPersonaCreation() {
+
+ //final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; //NON-NLS
+ //final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
+
+ try {
+
+ // Step 1: Create an account
+ CentralRepoAccount phoneAccount1 = CentralRepository.getInstance()
+ .getOrCreateAccount(phoneAccountType, PHONE_NUM_1);
+
+
+ // Step 2: Create a Persona for the Account
+
+ String comment = "The best dog ever";
+ Persona.PersonaStatus status = Persona.PersonaStatus.ACTIVE;
+ PersonaAccount pa1 = Persona.createPersonaForAccount(DOG_PERSONA_NAME, comment , status, phoneAccount1, "Because I said so", Persona.Confidence.LOW );
+
+
+ Persona dogPersona = pa1.getPersona();
+
+ // Verify Persona name, status etc.
+ Assert.assertEquals(DOG_PERSONA_NAME, pa1.getPersona().getName());
+ Assert.assertEquals(status.name(), dogPersona.getStatus().name());
+ Assert.assertTrue(dogPersona.getExaminer().getLoginName().equalsIgnoreCase(pa1.getExaminer().getLoginName()));
+
+ // Assert that the persona was created by the currently logged in user
+ Assert.assertTrue(dogPersona.getExaminer().getLoginName().equalsIgnoreCase(System.getProperty("user.name")));
+
+ // Assert that Persona was created within the last 10 mins
+ Assert.assertTrue(Instant.now().toEpochMilli() - pa1.getDateAdded() < 600 * 1000);
+ Assert.assertEquals(pa1.getConfidence(), Persona.Confidence.LOW);
+
+ // Step 3. Add Persona Aliases
+ PersonaAlias alias1 = dogPersona.addAlias("Good Boy", "Coz he's is the best dog ever", Persona.Confidence.MEDIUM);
+ PersonaAlias alias2 = dogPersona.addAlias("WoofWoof", "How many dumb comments can I come up with?", Persona.Confidence.LOW);
+
+ Assert.assertNotNull(alias1);
+ Assert.assertNotNull(alias2);
+
+ // get all aliases for persona
+ Collection aliases = dogPersona.getAliases();
+ Assert.assertEquals(2, aliases.size());
+ for (PersonaAlias alias: aliases) {
+ //System.out.println("Alias: "+ alias.getAlias()) ;
+ Assert.assertFalse(alias.getAlias().isEmpty());
+ }
+
+
+ //Step 4: Add Persona metadata
+ PersonaMetadata metadata1 = dogPersona.addMetadata("Color", "Black", "He's got thick black hair.", Persona.Confidence.MEDIUM);
+ PersonaMetadata metadata2 = dogPersona.addMetadata("Gender", "Male", "Because...", Persona.Confidence.LOW);
+
+ Assert.assertNotNull(metadata1);
+ Assert.assertNotNull(metadata2);
+
+ // get all metadata for persona
+ Collection metadataList = dogPersona.getMetadata();
+ Assert.assertEquals(2, metadataList.size());
+ for (PersonaMetadata md: metadataList) {
+ //System.out.println(String.format("Metadata: %s : %s", md.getName(), md.getValue())) ;
+ Assert.assertFalse(md.getName().isEmpty());
+ Assert.assertFalse(md.getValue().isEmpty());
+ }
+
+
+ // Step 5: associate another account with same persona
+ CentralRepoAccount catdogFBAccount = CentralRepository.getInstance()
+ .getOrCreateAccount(facebookAccountType, FACEBOOK_ID_CATDOG);
+
+ // Add an account to persona
+ dogPersona.addAccountToPersona(catdogFBAccount, "Looks like dog, barks like a dog...", Persona.Confidence.MEDIUM);
+
+ // Get all acounts for the persona...
+ Collection personaAccounts = dogPersona.getPersonaAccounts();
+
+ Assert.assertEquals(2, personaAccounts.size());
+
+ for (PersonaAccount pa: personaAccounts) {
+ //System.out.println(String.format("PersonaAccount: Justification = %s : Date Added = %s", pa.getJustification(), DATE_FORMAT.format(new Date(pa.getDateAdded())))) ;
+ Assert.assertFalse(pa.getJustification().isEmpty());
+ Assert.assertFalse(pa.getAccount().getTypeSpecificId().isEmpty());
+ Assert.assertTrue(pa.getDateAdded() > 0);
+ Assert.assertTrue(pa.getPersona().getCreatedDate()> 0);
+ }
+
+ // Step 6: Create a Second Persona, that shares a common account with another persona
+
+ String comment2 = "The fiercest cat alive.";
+ PersonaAccount pa2 = Persona.createPersonaForAccount(CAT_PERSONA_NAME, comment2 , Persona.PersonaStatus.ACTIVE, catdogFBAccount, "Smells like a cat.", Persona.Confidence.LOW );
+ Assert.assertNotNull(pa2);
+ Assert.assertTrue(pa2.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
+
+
+ // Get ALL personas for an account
+ Collection personaAccounts2 = PersonaAccount.getPersonaAccountsForAccount(catdogFBAccount.getAccountId());
+
+ Assert.assertEquals(2, personaAccounts2.size());
+ for (PersonaAccount pa: personaAccounts2) {
+ //System.out.println(String.format("PersonaAccount: Justification = %s : Date Added = %s", pa.getJustification(), DATE_FORMAT.format(new Date(pa.getDateAdded())))) ;
+ Assert.assertFalse(pa.getJustification().isEmpty());
+ Assert.assertFalse(pa.getAccount().getTypeSpecificId().isEmpty());
+ Assert.assertTrue(pa.getDateAdded() > 0);
+ Assert.assertTrue(pa.getPersona().getCreatedDate()> 0);
+ Assert.assertFalse(pa.getPersona().getName().isEmpty());
+ }
+
+
+ } catch (CentralRepoException ex) {
+ Assert.fail("Didn't expect an exception here. Exception: " + ex);
+ }
+ }
+
+ /**
+ * Tests Personas & X_Accounts and X_instances in the context of Case/data source.
+ * There are 4 Cases.
+ * - Case1 has 2 data sources, case2 has 1.
+ * - Case3 has 2 data sources, case4 has 1.
+ * There are 3 personas - A Cat, a Dog, and Sherlock Holmes.
+ * Cat & Dog share a FB account - with 3 instances split over the 2 cases - Case1 & Case2.
+ * Dog has his his own email account - with one instance in Case1
+ * Cat has his own WhatsApp account. - with 2 instances - in Case1 & Case2
+ * Sherlock has a Skype account - with 1 instance in Case 3.
+ * Case 4 has no personas or accounts
+ */
+ public void testPersonaWithCases() {
+
+ try {
+ // Create an account - Cat and Dog have a shared FB account
+ CentralRepoAccount catdogFBAccount = CentralRepository.getInstance()
+ .getOrCreateAccount(facebookAccountType, FACEBOOK_ID_CATDOG);
+
+
+ // Create account instance attribute for that account, on Case 1, DS 1
+ CorrelationAttributeInstance fbAcctInstance1 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
+ -1,
+ case1,
+ dataSource1fromCase1,
+ "path1",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1001L,
+ catdogFBAccount.getAccountId());
+ CentralRepository.getInstance().addArtifactInstance(fbAcctInstance1);
+
+
+ // Create account instance attribute for that account, on Case 1, DS 2
+ CorrelationAttributeInstance fbAcctInstance2 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
+ -1,
+ case1,
+ dataSource2fromCase1,
+ "path2",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1002L, catdogFBAccount.getAccountId());
+
+ CentralRepository.getInstance().addArtifactInstance(fbAcctInstance2);
+
+
+ // Create account instance attribute for that account, on Case 1, DS 2
+ CorrelationAttributeInstance fbAcctInstance3 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
+ -1,
+ case2,
+ dataSource1fromCase2,
+ "path3",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1003L, catdogFBAccount.getAccountId());
+ CentralRepository.getInstance().addArtifactInstance(fbAcctInstance3);
+
+
+ // Create Persona for the Dog, using the shared FB account
+ String comment = "The best dog ever";
+ Persona.PersonaStatus status = Persona.PersonaStatus.ACTIVE;
+ PersonaAccount pa1 = Persona.createPersonaForAccount(DOG_PERSONA_NAME,
+ comment ,
+ status, catdogFBAccount, "Because I said so", Persona.Confidence.LOW );
+ Persona dogPersona = pa1.getPersona();
+
+
+
+ // create a second persona for the same account - Cat has the same FB account as dog
+ String comment2 = "The fiercest cat alive.";
+ PersonaAccount pa2 = Persona.createPersonaForAccount(CAT_PERSONA_NAME,
+ comment2 , Persona.PersonaStatus.ACTIVE,
+ catdogFBAccount, "Smells like a cat.", Persona.Confidence.LOW );
+
+ Persona catPersona = pa2.getPersona();
+ Assert.assertNotNull(pa2);
+ Assert.assertTrue(pa2.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
+
+
+
+ // Add a 2nd account to the Dog - dog has his own email
+ CentralRepoAccount dogEmailAccount = CentralRepository.getInstance()
+ .getOrCreateAccount(emailAccountType, DOG_EMAIL_ID);
+
+ // Add an instance of dog email
+ CorrelationAttributeInstance dogEmailAcctInstance = new CorrelationAttributeInstance(emailInstanceType, DOG_EMAIL_ID,
+ -1,
+ case1,
+ dataSource2fromCase1,
+ "path3",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1002L,
+ dogEmailAccount.getAccountId());
+
+ CentralRepository.getInstance().addArtifactInstance(dogEmailAcctInstance);
+
+ PersonaAccount pa3 = dogPersona.addAccountToPersona(dogEmailAccount, "Thats definitely a dog email account", Persona.Confidence.MEDIUM);
+ Assert.assertNotNull(pa3);
+ Assert.assertTrue(pa3.getPersona().getName().equalsIgnoreCase(DOG_PERSONA_NAME));
+
+
+ // create a WhatsApp account for cat, add 2 instances, and then add that to Cat persona
+ CentralRepoAccount catWhatsAppAccount = CentralRepository.getInstance()
+ .getOrCreateAccount(whatsAppAccountType, CAT_WHATSAPP_ID);
+
+ // Add 2 instances of cat whatsApp
+ CorrelationAttributeInstance catWhatsAppAccountInstance1 = new CorrelationAttributeInstance(whatsAppInstanceType, CAT_WHATSAPP_ID,
+ -1,
+ case1,
+ dataSource1fromCase1,
+ "path4",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1005L,
+ catWhatsAppAccount.getAccountId());
+ CentralRepository.getInstance().addArtifactInstance(catWhatsAppAccountInstance1);
+
+ CorrelationAttributeInstance catWhatsAppAccountInstance2 = new CorrelationAttributeInstance(whatsAppInstanceType, CAT_WHATSAPP_ID,
+ -1,
+ case2,
+ dataSource1fromCase2,
+ "path5",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1006L,
+ catWhatsAppAccount.getAccountId());
+ CentralRepository.getInstance().addArtifactInstance(catWhatsAppAccountInstance2);
+
+
+ PersonaAccount pa4 = catPersona.addAccountToPersona(catWhatsAppAccount, "The cat has a WhatsApp account", Persona.Confidence.MEDIUM);
+ Assert.assertNotNull(pa4);
+ Assert.assertTrue(pa4.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
+
+
+
+ Collection dogPersonaAccounts = dogPersona.getPersonaAccounts();
+ Assert.assertEquals(2, dogPersonaAccounts.size()); // Dog has 2 accounts.
+ for (PersonaAccount pa : dogPersonaAccounts) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().equalsIgnoreCase(FACEBOOK_ID_CATDOG)
+ || pa.getAccount().getTypeSpecificId().equalsIgnoreCase(DOG_EMAIL_ID));
+ // System.out.println("Dog Account id : " + acct.getTypeSpecificId());
+ }
+
+
+ Collection catPersonaAccounts = catPersona.getPersonaAccounts();
+ Assert.assertEquals(2, catPersonaAccounts.size()); // cat has 2 accounts.
+ for (PersonaAccount pa:catPersonaAccounts) {
+ //System.out.println("Cat Account id : " + acct.getTypeSpecificId());
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().equalsIgnoreCase(FACEBOOK_ID_CATDOG)
+ || pa.getAccount().getTypeSpecificId().equalsIgnoreCase(CAT_WHATSAPP_ID));
+ }
+
+ // create account and Persona for Sherlock Holmes.
+ // Create a Skype Account
+ CentralRepoAccount holmesSkypeAccount = CentralRepository.getInstance()
+ .getOrCreateAccount(skypeAccountType, HOLMES_SKYPE_ID);
+
+ // Add an instance of Skype account to Case3/DS1
+ CorrelationAttributeInstance skypeAcctInstance = new CorrelationAttributeInstance(skypeInstanceType, HOLMES_SKYPE_ID,
+ -1,
+ case3,
+ dataSource1fromCase3,
+ "path8",
+ "",
+ TskData.FileKnown.UNKNOWN,
+ 1011L,
+ holmesSkypeAccount.getAccountId());
+ CentralRepository.getInstance().addArtifactInstance(skypeAcctInstance);
+
+
+ // Create a person for the Skype account
+ PersonaAccount pa5 = Persona.createPersonaForAccount(HOLMES_PERSONA_NAME,
+ "Has a Pipe in his mouth." , Persona.PersonaStatus.ACTIVE,
+ holmesSkypeAccount, "The name says it all.", Persona.Confidence.LOW );
+
+ Persona holmesPersona = pa5.getPersona();
+ Assert.assertNotNull(pa5);
+ Assert.assertTrue(pa5.getPersona().getName().equalsIgnoreCase(HOLMES_PERSONA_NAME));
+
+
+
+ // Test that getting cases for Persona
+ Collection dogCases = dogPersona.getCases();
+ Assert.assertEquals(2, dogCases.size()); // dog appears in 2 cases.
+ for (CorrelationCase dc: dogCases) {
+ Assert.assertTrue(dc.getCaseUUID().equalsIgnoreCase(CASE_1_UUID)
+ || dc.getCaseUUID().equalsIgnoreCase(CASE_2_UUID));
+ //System.out.println("Dog Case UUID : " + dc.getCaseUUID());
+ }
+
+ Collection catCases = catPersona.getCases();
+ Assert.assertEquals(2, catCases.size()); // cat appears in 2 cases.
+ for (CorrelationCase cc: catCases) {
+ Assert.assertTrue(cc.getCaseUUID().equalsIgnoreCase(CASE_1_UUID)
+ || cc.getCaseUUID().equalsIgnoreCase(CASE_2_UUID));
+ //System.out.println("Cat Case UUID : " + cc.getCaseUUID());
+ }
+
+ Collection holmesCases = holmesPersona.getCases();
+ Assert.assertEquals(1, holmesCases.size()); // Holmes appears in 1 case.
+ for (CorrelationCase hc: holmesCases) {
+ Assert.assertTrue(hc.getCaseUUID().equalsIgnoreCase(CASE_3_UUID));
+ //System.out.println("Holmes Case UUID : " + hc.getCaseUUID());
+ }
+
+
+ // Test that getting data sources for the Persona -
+ Collection dogDatasources = dogPersona.getDataSources();
+ Assert.assertEquals(3, dogDatasources.size()); // dog appears in 2 cases in 3 data sources.
+ for (CorrelationDataSource dds: dogDatasources) {
+ Assert.assertTrue(dds.getDeviceID().equalsIgnoreCase(DS1_DEVICEID)
+ || dds.getDeviceID().equalsIgnoreCase(DS2_DEVICEID)
+ || dds.getDeviceID().equalsIgnoreCase(DS3_DEVICEID));
+ //System.out.println("Dog DS DeviceID : " + dds.getDeviceID());
+ }
+
+ Collection catDatasources = catPersona.getDataSources();
+ Assert.assertEquals(3, catDatasources.size()); // cat appears in 2 cases in 3 data sources.
+ for (CorrelationDataSource cds: catDatasources) {
+ Assert.assertTrue(cds.getDeviceID().equalsIgnoreCase(DS1_DEVICEID)
+ || cds.getDeviceID().equalsIgnoreCase(DS2_DEVICEID)
+ || cds.getDeviceID().equalsIgnoreCase(DS3_DEVICEID));
+ //System.out.println("Cat DS DeviceID : " + cds.getDeviceID());
+ }
+
+ Collection holmesDatasources = holmesPersona.getDataSources();
+ Assert.assertEquals(1, holmesDatasources.size()); // Holmes appears in 1 cases in 1 data source.
+ for (CorrelationDataSource hds: holmesDatasources) {
+ Assert.assertTrue(hds.getDeviceID().equalsIgnoreCase(DS4_DEVICEID));
+ //System.out.println("Holmes DS DeviceID : " + hds.getDeviceID());
+ }
+
+ // Test getting peronas by case.
+
+ // Test that getting all Personas for Case 1 - Case1 has 2 persona - Cat & Dog
+ Collection case1Persona = Persona.getPersonasForCase(case1);
+ Assert.assertEquals(2, case1Persona.size()); //
+
+ // Test that getting all Personas for Case 2 - Case2 has 2 persona - Cat & Dog
+ Collection case2Persona = Persona.getPersonasForCase(case2);
+ Assert.assertEquals(2, case2Persona.size()); //
+
+ // Test that getting all Personas for Case 3 - Case3 has 1 persona - Holmes
+ Collection case3Persona = Persona.getPersonasForCase(case3);
+ Assert.assertEquals(1, case3Persona.size()); //
+
+ // Test that getting all Personas for Case 4 - Case4 has no persona
+ Collection case4Persona = Persona.getPersonasForCase(case4);
+ Assert.assertEquals(0, case4Persona.size()); //
+
+
+ // Test getting peronas by data source.
+
+ // Test that getting all Personas for DS 1
+ Collection ds1Persona = Persona.getPersonasForDataSource(dataSource1fromCase1);
+ Assert.assertEquals(2, ds1Persona.size()); //
+
+ Collection ds2Persona = Persona.getPersonasForDataSource(dataSource2fromCase1);
+ Assert.assertEquals(2, ds2Persona.size()); //
+
+ Collection ds3Persona = Persona.getPersonasForDataSource(dataSource1fromCase2);
+ Assert.assertEquals(2, ds3Persona.size()); //
+
+ Collection ds4Persona = Persona.getPersonasForDataSource(dataSource1fromCase3);
+ Assert.assertEquals(1, ds4Persona.size()); //
+
+ Collection ds5Persona = Persona.getPersonasForDataSource(dataSource2fromCase3);
+ Assert.assertEquals(0, ds5Persona.size()); //
+
+ Collection ds6Persona = Persona.getPersonasForDataSource(dataSource1fromCase4);
+ Assert.assertEquals(0, ds6Persona.size()); //
+
+
+ }
+ catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
+ Exceptions.printStackTrace(ex);
+ Assert.fail(ex.getMessage());
+ }
+
+ }
+ /**
+ * Tests edge cases, error cases
+ */
+ public void testPersonaCreationEdgeCases() {
+
+ // Test1: create Persona without specifying a name
+ {
+ try {
+ // Create an email account
+ CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
+ .getOrCreateAccount(emailAccountType, EMAIL_ID_1);
+
+ // Create a Persona with no name
+ PersonaAccount pa1 = Persona.createPersonaForAccount(null, "A persona with no name",
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "The person lost his name", Persona.Confidence.LOW);
+
+ // Verify Persona has a default name
+ Assert.assertEquals("Unknown", pa1.getPersona().getName());
+
+ } catch (CentralRepoException ex) {
+ Assert.fail("No name persona test failed. Exception: " + ex);
+ }
+ }
+
+
+ }
+
+ /**
+ * Tests searching of Persona by persona name.
+ */
+ public void testPersonaSearchByName() {
+
+ // Test1: create Personas with similar names.
+ {
+ try {
+ // Create an email account
+ CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
+ .getOrCreateAccount(emailAccountType, EMAIL_ID_1);
+
+ // Create all personas with same comment.
+ final String personaComment = "Creator of Jungle Book.";
+
+ // Create a Persona with name "Rudyard Kipling"
+ Persona.createPersonaForAccount("Rudyard Kipling", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+ // Create a Persona with name "Rudy"
+ Persona.createPersonaForAccount("Rudy", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+
+ // Create a Persona with name "Kipling Senior"
+ Persona.createPersonaForAccount("Kipling Senior", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+ // Create a Persona with name "Senor Kipling"
+ Persona.createPersonaForAccount("Senor Kipling", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+
+ // Test 1 Search "kipling" - expect 3 matches
+ Collection personaSearchResult = Persona.getPersonaByName("kipling");
+ Assert.assertEquals(3, personaSearchResult.size());
+ for (Persona p: personaSearchResult) {
+ Assert.assertTrue(p.getComment().equalsIgnoreCase(personaComment));
+ }
+
+ // Search 'Rudy' - expect 2 matches
+ personaSearchResult = Persona.getPersonaByName("Rudy");
+ Assert.assertEquals(2, personaSearchResult.size());
+
+
+ // Search 'Sen' - expect 2 matches
+ personaSearchResult = Persona.getPersonaByName("Sen");
+ Assert.assertEquals(2, personaSearchResult.size());
+
+
+ // Search 'IPL' - expect 3 matches
+ personaSearchResult = Persona.getPersonaByName("IPL");
+ Assert.assertEquals(3, personaSearchResult.size());
+
+
+ // Serach "Rudyard Kipling" - expect 1 match
+ personaSearchResult = Persona.getPersonaByName("Rudyard Kipling");
+ Assert.assertEquals(1, personaSearchResult.size());
+ Assert.assertTrue(personaSearchResult.iterator().next().getName().equalsIgnoreCase("Rudyard Kipling"));
+
+ // Search '' - expect ALL (4) to match
+ personaSearchResult = Persona.getPersonaByName("");
+ Assert.assertEquals(4, personaSearchResult.size());
+
+
+ } catch (CentralRepoException ex) {
+ Assert.fail("No name persona test failed. Exception: " + ex);
+ }
+ }
+
+
+ }
+
+
+ /**
+ * Tests searching of Persona by account identifier substrings.
+ */
+ public void testPersonaSearchByAccountIdentifier() {
+
+ // Test1: create Personas with similar names.
+ {
+ try {
+ // Create an email account1
+ CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
+ .getOrCreateAccount(emailAccountType, "joeexotic555@yahoo.com");
+
+ // Create all personas with same comment.
+ final String personaComment = "Comment used to create a persona";
+
+ // Create a Persona with name "Joe Exotic" associated with the email address
+ Persona.createPersonaForAccount("Joe Exotic", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+ // Create a Persona with name "Tiger King" associated with the email address
+ Persona.createPersonaForAccount("Tiger King", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
+
+
+
+ // Create an phone account with number "+1 999 555 3366"
+ CentralRepoAccount phoneAccount1 = CentralRepository.getInstance()
+ .getOrCreateAccount(phoneAccountType, "+1 999 555 3366");
+
+
+ // Create a Persona with name "Carol Baskin" associated
+ Persona.createPersonaForAccount("Carol Baskin", personaComment,
+ Persona.PersonaStatus.ACTIVE, phoneAccount1, "", Persona.Confidence.LOW);
+
+ // Create a Persona with no name assoctaed with
+ Persona.createPersonaForAccount(null, personaComment,
+ Persona.PersonaStatus.ACTIVE, phoneAccount1, "", Persona.Confidence.LOW);
+
+
+
+ // Create another email account1
+ CentralRepoAccount emailAccount2 = CentralRepository.getInstance()
+ .getOrCreateAccount(emailAccountType, "jodoe@mail.com");
+
+
+
+ // Create a Persona with name "John Doe" associated with the email address
+ Persona.createPersonaForAccount("John Doe", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount2, "", Persona.Confidence.LOW);
+
+ Persona.createPersonaForAccount("Joanne Doe", personaComment,
+ Persona.PersonaStatus.ACTIVE, emailAccount2, "", Persona.Confidence.LOW);
+
+
+
+ // Test1 Search on 'joe' - should get 2
+ Collection personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("joe");
+ Assert.assertEquals(2, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("joe"));
+ }
+
+ // Search on 'exotic' - should get 2
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("exotic");
+ Assert.assertEquals(2, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("exotic"));
+ }
+
+ // Test1 Search on '999' - should get 2
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("999");
+ Assert.assertEquals(2, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("999"));
+ }
+
+ // Test1 Search on '555' - should get 4
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("555");
+ Assert.assertEquals(4, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("555"));
+ }
+
+ // Test1 Search on 'doe' - should get 2
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("doe");
+ Assert.assertEquals(2, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("doe"));
+ }
+
+ // Test1 Search on '@' - should get 4
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("@");
+ Assert.assertEquals(4, personaSearchResult.size());
+ for (PersonaAccount pa: personaSearchResult) {
+ Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("@"));
+ }
+
+ // Test1 Search on '' - should get ALL (6)
+ personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("");
+ Assert.assertEquals(6, personaSearchResult.size());
+
+
+ } catch (CentralRepoException ex) {
+ Assert.fail("No name persona test failed. Exception: " + ex);
+ }
+ }
+
+ }
+
+ /**
+ * Tests the getOrInsertExaminer() api.
+ */
+ public void testExaminers() {
+
+ try {
+ String examinerName = "abcdefg";
+ CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
+ Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
+
+ examinerName = "";
+ examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
+ Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
+
+ examinerName = "D'Aboville";
+ examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
+ Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
+
+ } catch (CentralRepoException ex) {
+ Assert.fail("Examiner tests failed. Exception: " + ex);
+ }
+
+ }
+}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
index 9d8c8b1269..5ab3b8c84b 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
@@ -79,6 +79,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@@ -107,6 +108,8 @@ public final class ImageGalleryController {
Case.Events.CONTENT_TAG_DELETED,
Case.Events.DATA_SOURCE_DELETED
);
+
+ private static final String CATEGORY_TAG_SET_PREFIX = "Project VIC";
/*
* There is an image gallery controller per case. It is created during the
@@ -228,14 +231,16 @@ public final class ImageGalleryController {
void startUp() throws TskCoreException {
selectionModel = new FileIDSelectionModel(this);
thumbnailCache = new ThumbnailCache(this);
+
+ TagSet categoryTagSet = getCategoryTagSet();
/*
* TODO (JIRA-5212): The next two lines need to be executed in this
* order. Why? This suggests there is some inappropriate coupling
* between the DrawableDB and GroupManager classes.
*/
groupManager = new GroupManager(this);
- drawableDB = DrawableDB.getDrawableDB(this);
- categoryManager = new CategoryManager(this);
+ drawableDB = DrawableDB.getDrawableDB(this, categoryTagSet);
+ categoryManager = new CategoryManager(this, categoryTagSet);
tagsManager = new DrawableTagsManager(this);
tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager);
@@ -720,6 +725,28 @@ public final class ImageGalleryController {
private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
}
+
+ /**
+ * Returns the TagSet with the image gallery categories.
+ *
+ * @return Category TagSet.
+ *
+ * @throws TskCoreException
+ */
+ private TagSet getCategoryTagSet() throws TskCoreException {
+ List tagSetList = getCaseDatabase().getTaggingManager().getTagSets();
+ if (tagSetList != null && !tagSetList.isEmpty()) {
+ for (TagSet set : tagSetList) {
+ if (set.getName().startsWith(CATEGORY_TAG_SET_PREFIX)) {
+ return set;
+ }
+ }
+ // If we get to here the Project VIC Test set wasn't found;
+ throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
+ } else {
+ throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
+ }
+ }
/**
* A listener for ingest module application events.
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java
index 6ea1e91769..125f5489ca 100755
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java
@@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
- * A task that updates one drawable file in the drawables database.
+ * A task that updates one drawable file in the drawable database.
*/
class UpdateDrawableFileTask extends DrawableDbTask {
@@ -60,5 +60,5 @@ class UpdateDrawableFileTask extends DrawableDbTask {
Logger.getLogger(UpdateDrawableFileTask.class.getName()).log(Level.SEVERE, "Error in update file task", ex); //NON-NLS
}
}
-
+
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java
index d847a21a58..13703f417b 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java
@@ -19,6 +19,9 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableMap;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -27,9 +30,10 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.collections.ObservableSet;
+import javafx.embed.swing.SwingFXUtils;
+import javafx.scene.Node;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
-import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javax.annotation.Nonnull;
@@ -41,9 +45,7 @@ import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.DrawableDbTask;
-import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
@@ -51,6 +53,7 @@ import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
+import javafx.scene.image.ImageView;
/**
* An action that associates a drawable file with a Project Vic category.
@@ -62,24 +65,24 @@ public class CategorizeAction extends Action {
private final ImageGalleryController controller;
private final UndoRedoManager undoManager;
- private final DhsImageCategory cat;
private final Set selectedFileIDs;
private final Boolean createUndo;
+ private final TagName tagName;
- public CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set selectedFileIDs) {
- this(controller, cat, selectedFileIDs, true);
+ public CategorizeAction(ImageGalleryController controller, TagName tagName, Set selectedFileIDs) {
+ this(controller, tagName, selectedFileIDs, true);
}
- private CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set selectedFileIDs, Boolean createUndo) {
- super(cat.getDisplayName());
+ private CategorizeAction(ImageGalleryController controller, TagName tagName, Set selectedFileIDs, Boolean createUndo) {
+ super(tagName.getDisplayName());
this.controller = controller;
this.undoManager = controller.getUndoManager();
- this.cat = cat;
this.selectedFileIDs = selectedFileIDs;
this.createUndo = createUndo;
- setGraphic(cat.getGraphic());
+ this.tagName = tagName;
+ setGraphic(getGraphic(tagName));
setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs));
- setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
+ setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(getCategoryNumberFromTagName(tagName))));
}
static public Menu getCategoriesMenu(ImageGalleryController controller) {
@@ -87,8 +90,18 @@ public class CategorizeAction extends Action {
}
final void addCatToFiles(Set ids) {
- Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS
- controller.queueDBTask(new CategorizeDrawableFileTask(ids, cat, createUndo));
+ Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), tagName.getDisplayName()}); //NON-NLS
+ controller.queueDBTask(new CategorizeDrawableFileTask(ids, tagName, createUndo));
+ }
+
+ private String getCategoryNumberFromTagName(TagName tagName) {
+ String displayName = tagName.getDisplayName();
+ if (displayName.contains("CAT")) {
+ String[] split = displayName.split(":");
+ split = split[0].split("-");
+ return split[1];
+ }
+ return "";
}
/**
@@ -104,8 +117,8 @@ public class CategorizeAction extends Action {
// Each category get an item in the sub-menu. Selecting one of these menu items adds
// a tag with the associated category.
- for (final DhsImageCategory cat : DhsImageCategory.values()) {
- MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
+ for (TagName tagName : controller.getCategoryManager().getCategories()) {
+ MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, tagName, selected));
getItems().add(categoryItem);
}
}
@@ -124,54 +137,39 @@ public class CategorizeAction extends Action {
final Set fileIDs;
final boolean createUndo;
- final DhsImageCategory cat;
+ final TagName catTagName;
- CategorizeDrawableFileTask(Set fileIDs, @Nonnull DhsImageCategory cat, boolean createUndo) {
+ CategorizeDrawableFileTask(Set fileIDs, @Nonnull TagName catTagName, boolean createUndo) {
super();
this.fileIDs = fileIDs;
- java.util.Objects.requireNonNull(cat);
- this.cat = cat;
+ java.util.Objects.requireNonNull(catTagName);
+ this.catTagName = catTagName;
this.createUndo = createUndo;
}
@Override
public void run() {
final DrawableTagsManager tagsManager = controller.getTagsManager();
- final CategoryManager categoryManager = controller.getCategoryManager();
- Map oldCats = new HashMap<>();
- TagName tagName = categoryManager.getTagName(cat);
+ Map oldCats = new HashMap<>();
for (long fileID : fileIDs) {
try {
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
if (createUndo) {
- DhsImageCategory oldCat = file.getCategory(); //drawable db access
- TagName oldCatTagName = categoryManager.getTagName(oldCat);
- if (false == tagName.equals(oldCatTagName)) {
- oldCats.put(fileID, oldCat);
+ TagName oldCatTagName = file.getCategory(); //drawable db access
+ if (false == catTagName.equals(oldCatTagName)) {
+ oldCats.put(fileID, oldCatTagName);
}
}
final List fileTags = tagsManager.getContentTags(file);
- if (tagName == categoryManager.getTagName(DhsImageCategory.ZERO)) {
- // delete all cat tags for cat-0
- fileTags.stream()
- .filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
- .forEach((ct) -> {
- try {
- tagsManager.deleteContentTag(ct);
- } catch (TskCoreException ex) {
- logger.log(Level.SEVERE, "Error removing old categories result", ex); //NON-NLS
- }
- });
- } else {
- //add cat tag if no existing cat tag for that cat
- if (fileTags.stream()
- .map(Tag::getName)
- .filter(tagName::equals)
- .collect(Collectors.toList()).isEmpty()) {
- tagsManager.addContentTag(file, tagName, "");
- }
+
+ if (fileTags.stream()
+ .map(Tag::getName)
+ .filter(tagName::equals)
+ .collect(Collectors.toList()).isEmpty()) {
+ tagsManager.addContentTag(file, tagName, "");
}
+
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error categorizing result", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
@@ -183,7 +181,7 @@ public class CategorizeAction extends Action {
}
if (createUndo && oldCats.isEmpty() == false) {
- undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
+ undoManager.addToUndo(new CategorizationChange(controller, catTagName, oldCats));
}
}
}
@@ -194,14 +192,14 @@ public class CategorizeAction extends Action {
@Immutable
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
- private final DhsImageCategory newCategory;
- private final ImmutableMap oldCategories;
+ private final TagName newTagNameCategory;
+ private final ImmutableMap oldTagNameCategories;
private final ImageGalleryController controller;
- CategorizationChange(ImageGalleryController controller, DhsImageCategory newCategory, Map oldCategories) {
+ CategorizationChange(ImageGalleryController controller, TagName newTagNameCategory, Map oldTagNameCategories) {
this.controller = controller;
- this.newCategory = newCategory;
- this.oldCategories = ImmutableMap.copyOf(oldCategories);
+ this.newTagNameCategory = newTagNameCategory;
+ this.oldTagNameCategories = ImmutableMap.copyOf(oldTagNameCategories);
}
/**
@@ -210,7 +208,7 @@ public class CategorizeAction extends Action {
*/
@Override
public void run() {
- new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false)
+ new CategorizeAction(controller, newTagNameCategory, this.oldTagNameCategories.keySet(), false)
.handle(null);
}
@@ -221,10 +219,42 @@ public class CategorizeAction extends Action {
@Override
public void undo() {
- for (Map.Entry entry : oldCategories.entrySet()) {
+ for (Map.Entry entry : oldTagNameCategories.entrySet()) {
new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
.handle(null);
}
}
}
+
+ /**
+ * Create an BufferedImage to use as the icon for the given TagName.
+ *
+ * @param tagName The category TagName.
+ *
+ * @return TagName Icon BufferedImage.
+ */
+ private BufferedImage getImageForTagName(TagName tagName) {
+ BufferedImage off_image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = off_image.createGraphics();
+
+ g2.setColor(java.awt.Color.decode(tagName.getColor().getRgbValue()));
+ g2.fillRect(0, 0, 16, 16);
+
+ g2.setColor(Color.BLACK);
+ g2.drawRect(0, 0, 16, 16);
+ return off_image;
+ }
+
+ /**
+ * Returns a Node which is a ImageView of the icon for the given TagName.
+ *
+ * @param tagname
+ *
+ * @return Node for use as the TagName menu item graphic.
+ */
+ private Node getGraphic(TagName tagname) {
+ BufferedImage buff_image = getImageForTagName(tagname);
+ return new ImageView(SwingFXUtils.toFXImage(buff_image, null));
+ }
+
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java
index b49e1ca30c..20e8cb2081 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java
@@ -25,6 +25,7 @@ import java.util.logging.Level;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
+import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
@@ -37,9 +38,9 @@ import javafx.scene.layout.VBox;
import static org.apache.commons.lang.ObjectUtils.notEqual;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
+import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -50,7 +51,7 @@ public class CategorizeGroupAction extends CategorizeAction {
private final static Logger LOGGER = Logger.getLogger(CategorizeGroupAction.class.getName());
- public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
+ public CategorizeGroupAction(TagName newCat, ImageGalleryController controller) {
super(controller, newCat, null);
setEventHandler(actionEvent -> {
controller.getViewState().getGroup().ifPresent(group -> {
@@ -60,12 +61,12 @@ public class CategorizeGroupAction extends CategorizeAction {
//if they have preveiously disabled the warning, just go ahead and apply categories.
addCatToFiles(ImmutableSet.copyOf(fileIDs));
} else {
- final Map catCountMap = new HashMap<>();
+ final Map catCountMap = new HashMap<>();
for (Long fileID : fileIDs) {
try {
- DhsImageCategory category = controller.getFileFromID(fileID).getCategory();
- if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
+ TagName category = controller.getFileFromID(fileID).getCategory();
+ if (category != null && newCat.equals(category) == false) {
catCountMap.merge(category, 1L, Long::sum);
}
} catch (TskCoreException ex) {
@@ -90,18 +91,19 @@ public class CategorizeGroupAction extends CategorizeAction {
"CategorizeGroupAction.fileCountMessage={0} with {1}",
"CategorizeGroupAction.dontShowAgain=Don't show this message again",
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
- private void showConfirmationDialog(final Map catCountMap, DhsImageCategory newCat, ObservableList fileIDs) {
+ private void showConfirmationDialog(final Map catCountMap, TagName newCat, ObservableList fileIDs) {
ButtonType categorizeButtonType
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
VBox textFlow = new VBox();
- for (Map.Entry entry : catCountMap.entrySet()) {
- if (entry.getValue() > 0
- && notEqual(entry.getKey(), newCat)) {
+ for (Map.Entry entry : catCountMap.entrySet()) {
+
+ if (entry != null && entry.getValue() > 0
+ && notEqual(entry.getKey(), newCat)) {
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
- entry.getKey().getGraphic());
+ getGraphic(entry.getKey()));
label.setContentDisplay(ContentDisplay.RIGHT);
textFlow.getChildren().add(label);
}
@@ -127,4 +129,8 @@ public class CategorizeGroupAction extends CategorizeAction {
}
});
}
+
+ public Node getGraphic(TagName tagName) {
+ return null;
+ }
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java
index bb8cd9de96..742ac300c1 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java
@@ -19,14 +19,14 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
+import org.sleuthkit.datamodel.TagName;
/**
*
*/
public class CategorizeSelectedFilesAction extends CategorizeAction {
- public CategorizeSelectedFilesAction(DhsImageCategory cat, ImageGalleryController controller) {
+ public CategorizeSelectedFilesAction(TagName cat, ImageGalleryController controller) {
super(controller, cat, null);
setEventHandler(actionEvent ->
addCatToFiles(controller.getSelectionModel().getSelected())
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java
index 2fb97b6e95..5eaf8b4ad7 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java
@@ -24,8 +24,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
import java.util.logging.Level;
@@ -33,11 +36,13 @@ import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
+import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
+import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -56,8 +61,6 @@ public class CategoryManager {
private static final Logger LOGGER = Logger.getLogger(CategoryManager.class.getName());
- private final ImageGalleryController controller;
-
/**
* the DrawableDB that backs the category counts cache. The counts are
* initialized from this, and the counting of CAT-0 is always delegated to
@@ -65,6 +68,8 @@ public class CategoryManager {
*/
private final DrawableDB drawableDb;
+ private final TagSet categoryTagSet;
+
/**
* Used to distribute CategoryChangeEvents
*/
@@ -79,32 +84,20 @@ public class CategoryManager {
* the count related methods go through this cache, which loads initial
* values from the database if needed.
*/
- private final LoadingCache categoryCounts
+ private final LoadingCache categoryCounts
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
- /**
- * cached TagNames corresponding to Categories, looked up from
- * autopsyTagManager at initial request or if invalidated by case change.
- */
- private final LoadingCache catTagNameMap
- = CacheBuilder.newBuilder().build(new CacheLoader() {
- @Override
- public TagName load(DhsImageCategory cat) throws TskCoreException {
- return getController().getTagsManager().getTagName(cat);
- }
- });
- public CategoryManager(ImageGalleryController controller) {
- this.controller = controller;
+ public CategoryManager(ImageGalleryController controller, TagSet categoryTagSet) throws TskCoreException {
this.drawableDb = controller.getDrawablesDatabase();
+ this.categoryTagSet = categoryTagSet;
}
- private ImageGalleryController getController() {
- return controller;
+ public List getCategories() {
+ return Collections.unmodifiableList(getSortedTagNames(categoryTagSet.getTagNames()));
}
synchronized public void invalidateCaches() {
categoryCounts.invalidateAll();
- catTagNameMap.invalidateAll();
fireChange(Collections.emptyList(), null);
}
@@ -115,16 +108,8 @@ public class CategoryManager {
*
* @return the number of files with the given Category
*/
- synchronized public long getCategoryCount(DhsImageCategory cat) {
- if (cat == DhsImageCategory.ZERO) {
- // Keeping track of the uncategorized files is a bit tricky while ingest
- // is going on, so always use the list of file IDs we already have along with the
- // other category counts instead of trying to track it separately.
- long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE);
- return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount;
- } else {
- return categoryCounts.getUnchecked(cat).sum();
- }
+ synchronized public long getCategoryCount(TagName tagName) {
+ return categoryCounts.getUnchecked(tagName).sum();
}
/**
@@ -133,10 +118,8 @@ public class CategoryManager {
*
* @param cat the Category to increment
*/
- synchronized public void incrementCategoryCount(DhsImageCategory cat) {
- if (cat != DhsImageCategory.ZERO) {
- categoryCounts.getUnchecked(cat).increment();
- }
+ synchronized public void incrementCategoryCount(TagName tagName) {
+ categoryCounts.getUnchecked(tagName).increment();
}
/**
@@ -145,10 +128,8 @@ public class CategoryManager {
*
* @param cat the Category to decrement
*/
- synchronized public void decrementCategoryCount(DhsImageCategory cat) {
- if (cat != DhsImageCategory.ZERO) {
- categoryCounts.getUnchecked(cat).decrement();
- }
+ synchronized public void decrementCategoryCount(TagName tagName) {
+ categoryCounts.getUnchecked(tagName).decrement();
}
/**
@@ -161,14 +142,14 @@ public class CategoryManager {
* @return a LongAdder whose value is set to the number of file with the
* given Category
*/
- synchronized private LongAdder getCategoryCountHelper(DhsImageCategory cat) {
+ synchronized private LongAdder getCategoryCountHelper(TagName cat) {
LongAdder longAdder = new LongAdder();
longAdder.decrement();
try {
longAdder.add(drawableDb.getCategoryCount(cat));
longAdder.increment();
} catch (IllegalStateException ex) {
- LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
+ LOGGER.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
}
return longAdder;
}
@@ -178,8 +159,8 @@ public class CategoryManager {
*
* @param fileIDs
*/
- public void fireChange(Collection fileIDs, DhsImageCategory newCategory) {
- categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory));
+ public void fireChange(Collection fileIDs, TagName tagName) {
+ categoryEventBus.post(new CategoryChangeEvent(fileIDs, tagName));
}
/**
@@ -216,68 +197,66 @@ public class CategoryManager {
}
/**
- * get the TagName used to store this Category in the main autopsy db.
+ * Returns true if the given TagName is a category tag.
*
- * @return the TagName used for this Category
+ * @param tName TagName
+ *
+ * @return True if tName is a category tag.
*/
- synchronized public TagName getTagName(DhsImageCategory cat) {
- return catTagNameMap.getUnchecked(cat);
+ public boolean isCategoryTagName(TagName tName) {
+ return categoryTagSet.getTagNames().contains(tName);
+ }
+
+ /**
+ * Returns true if the given TagName is not a category tag.
+ *
+ * Keep for use in location were a reference to this function is passed.
+ *
+ * @param tName TagName
+ *
+ * @return True if the given tName is not a category tag.
+ */
+ public boolean isNotCategoryTagName(TagName tName) {
+ return !isCategoryTagName(tName);
}
- public static DhsImageCategory categoryFromTagName(TagName tagName) {
- return DhsImageCategory.fromDisplayName(tagName.getDisplayName());
- }
-
- public static boolean isCategoryTagName(TagName tName) {
- return DhsImageCategory.isCategoryName(tName.getDisplayName());
- }
-
- public static boolean isNotCategoryTagName(TagName tName) {
- return DhsImageCategory.isNotCategoryName(tName.getDisplayName());
-
+ /**
+ * Returns the category tag set.
+ *
+ * @return
+ */
+ TagSet getCategorySet() {
+ return categoryTagSet;
}
@Subscribe
public void handleTagAdded(ContentTagAddedEvent event) {
final ContentTag addedTag = event.getAddedTag();
- if (isCategoryTagName(addedTag.getName())) {
- final DrawableTagsManager tagsManager = controller.getTagsManager();
- try {
- //remove old category tag(s) if necessary
- for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
- if (ct.getId() != addedTag.getId()
- && CategoryManager.isCategoryTagName(ct.getName())) {
- try {
- tagsManager.deleteContentTag(ct);
- } catch (TskCoreException tskException) {
- LOGGER.log(Level.SEVERE, "Failed to delete content tag. Unable to maintain categories in a consistent state.", tskException); //NON-NLS
- break;
- }
- }
- }
- } catch (TskCoreException tskException) {
- LOGGER.log(Level.SEVERE, "Failed to get content tags for content. Unable to maintain category in a consistent state.", tskException); //NON-NLS
- }
- DhsImageCategory newCat = CategoryManager.categoryFromTagName(addedTag.getName());
- if (newCat != DhsImageCategory.ZERO) {
- incrementCategoryCount(newCat);
- }
- fireChange(Collections.singleton(addedTag.getContent().getId()), newCat);
+ List removedTags = event.getDeletedTags();
+ if (removedTags != null) {
+ for (DeletedContentTagInfo tagInfo : removedTags) {
+ handleDeletedInfo(tagInfo);
+ }
+ }
+
+ if (isCategoryTagName(addedTag.getName())) {
+ incrementCategoryCount(addedTag.getName());
+ fireChange(Collections.singleton(addedTag.getContent().getId()), addedTag.getName());
}
}
@Subscribe
public void handleTagDeleted(ContentTagDeletedEvent event) {
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
+ handleDeletedInfo(deletedTagInfo);
+ }
+
+ private void handleDeletedInfo(DeletedContentTagInfo deletedTagInfo) {
TagName tagName = deletedTagInfo.getName();
if (isCategoryTagName(tagName)) {
-
- DhsImageCategory deletedCat = CategoryManager.categoryFromTagName(tagName);
- if (deletedCat != DhsImageCategory.ZERO) {
- decrementCategoryCount(deletedCat);
- }
+ decrementCategoryCount(tagName);
fireChange(Collections.singleton(deletedTagInfo.getContentID()), null);
}
}
@@ -290,16 +269,16 @@ public class CategoryManager {
public static class CategoryChangeEvent {
private final ImmutableSet fileIDs;
- private final DhsImageCategory newCategory;
+ private final TagName tagName;
- public CategoryChangeEvent(Collection fileIDs, DhsImageCategory newCategory) {
+ public CategoryChangeEvent(Collection fileIDs, TagName tagName) {
super();
this.fileIDs = ImmutableSet.copyOf(fileIDs);
- this.newCategory = newCategory;
+ this.tagName = tagName;
}
- public DhsImageCategory getNewCategory() {
- return newCategory;
+ public TagName getNewCategory() {
+ return tagName;
}
/**
@@ -309,4 +288,18 @@ public class CategoryManager {
return fileIDs;
}
}
+
+ private List getSortedTagNames(List tagNames) {
+ Comparator compareByDisplayName = new Comparator() {
+ @Override
+ public int compare(TagName tagName1, TagName tagName2) {
+ return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
+ }
+ };
+
+ List sortedTagNames = new ArrayList<>(tagNames);
+ sortedTagNames.sort(compareByDisplayName);
+
+ return sortedTagNames;
+ }
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java
index 25b50811b0..03ea2e3292 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -89,15 +88,17 @@ public class DrawableAttribute> {
* //TODO: this has lead to awkward hard to maintain code, and little
* advantage. move categories into DrawableDB?
*/
- public final static DrawableAttribute CATEGORY
- = new DrawableAttribute(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
+ public final static DrawableAttribute CATEGORY
+ = new DrawableAttribute(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
false,
"category-icon.png", //NON-NLS
f -> Collections.singleton(f.getCategory())) {
@Override
- public Node getGraphicForValue(DhsImageCategory val) {
- return val.getGraphic();
+ public Node getGraphicForValue(TagName val) {
+
+ return null;
+ //return val.getGraphic();
}
};
@@ -235,9 +236,13 @@ public class DrawableAttribute> {
.filter(value -> (value != null && value.toString().isEmpty() == false))
.collect(Collectors.toSet());
} catch (Exception ex) {
- /* There is a catch-all here because the code in the try block executes third-party
- library calls that throw unchecked exceptions. See JIRA-5144, where an IllegalStateException
- was thrown because a file's MIME type was incorrectly identified as a picture type. */
+ /*
+ * There is a catch-all here because the code in the try block
+ * executes third-party library calls that throw unchecked
+ * exceptions. See JIRA-5144, where an IllegalStateException was
+ * thrown because a file's MIME type was incorrectly identified as a
+ * picture type.
+ */
logger.log(Level.WARNING, "Exception while getting image attributes", ex); //NON-NLS
return Collections.emptySet();
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
index f634e8387d..3aa39bd2fd 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
@@ -52,11 +52,9 @@ import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
-import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
@@ -80,6 +78,7 @@ import org.sleuthkit.datamodel.TskDataException;
import org.sleuthkit.datamodel.VersionNumber;
import org.sqlite.SQLiteJDBCLoader;
import java.util.stream.Collectors;
+import org.sleuthkit.datamodel.TagSet;
/**
* Provides access to the image gallery database and selected tables in the case
@@ -250,7 +249,7 @@ public final class DrawableDB {
* could not be correctly initialized for Image
* Gallery use.
*/
- private DrawableDB(Path dbPath, ImageGalleryController controller) throws IOException, SQLException, TskCoreException {
+ private DrawableDB(Path dbPath, ImageGalleryController controller, TagSet standardCategories) throws IOException, SQLException, TskCoreException {
this.dbPath = dbPath;
this.controller = controller;
caseDb = this.controller.getCaseDatabase();
@@ -259,7 +258,7 @@ public final class DrawableDB {
dbWriteLock();
try {
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
- if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) {
+ if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups(standardCategories) || !removeDeletedDataSources() || !initializeImageList()) {
close();
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
}
@@ -297,12 +296,13 @@ public final class DrawableDB {
}
}
- private boolean initializeStandardGroups() {
+ private boolean initializeStandardGroups(TagSet standardCategories) {
CaseDbTransaction caseDbTransaction = null;
try {
caseDbTransaction = caseDb.beginTransaction();
- for (DhsImageCategory cat : DhsImageCategory.values()) {
- insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
+
+ for(TagName tagName: standardCategories.getTagNames()) {
+ insertGroup(tagName.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
}
caseDbTransaction.commit();
return true;
@@ -466,7 +466,7 @@ public final class DrawableDB {
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
- public static DrawableDB getDrawableDB(ImageGalleryController controller) throws TskCoreException {
+ public static DrawableDB getDrawableDB(ImageGalleryController controller, TagSet standardCategories) throws TskCoreException {
Path dbPath = ImageGalleryModule.getModuleOutputDir(controller.getCase()).resolve("drawable.db");
try {
deleteDatabaseIfOlderVersion(dbPath);
@@ -477,14 +477,14 @@ public final class DrawableDB {
}
try {
- return new DrawableDB(dbPath, controller);
+ return new DrawableDB(dbPath, controller, standardCategories);
} catch (IOException ex) {
throw new TskCoreException("Failed to create drawables database directory", ex); //NON-NLS
} catch (SQLException ex) {
throw new TskCoreException("Failed to create/open the drawables database", ex); //NON-NLS
}
}
-
+
/**
* Checks if the specified table exists in Drawable DB
*
@@ -2068,7 +2068,7 @@ public final class DrawableDB {
case MIME_TYPE:
return groupManager.getFileIDsWithMimeType((String) groupKey.getValue());
case CATEGORY:
- return groupManager.getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
+ return groupManager.getFileIDsWithCategory((TagName) groupKey.getValue());
case TAGS:
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
}
@@ -2269,9 +2269,8 @@ public final class DrawableDB {
*
* @return the number of the with the given category
*/
- public long getCategoryCount(DhsImageCategory cat) {
+ public long getCategoryCount(TagName tagName) {
try {
- TagName tagName = controller.getTagsManager().getTagName(cat);
if (nonNull(tagName)) {
return caseDb.getContentTagsByTagName(tagName).stream()
.map(ContentTag::getContent)
@@ -2280,7 +2279,7 @@ public final class DrawableDB {
.count();
}
} catch (IllegalStateException ex) {
- logger.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
+ logger.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
} catch (TskCoreException ex1) {
logger.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); //NON-NLS
}
@@ -2314,7 +2313,6 @@ public final class DrawableDB {
DrawableTagsManager tagsManager = controller.getTagsManager();
String catTagNameIDs = tagsManager.getCategoryTagNames().stream()
- .filter(tagName -> notEqual(tagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName()))
.map(TagName::getId)
.map(Object::toString)
.collect(Collectors.joining(",", "(", ")"));
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java
index 9c84f6d3b2..112c7ab6ec 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java
@@ -40,8 +40,8 @@ import org.apache.commons.lang3.text.WordUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
+import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -94,15 +94,19 @@ public abstract class DrawableFile {
private final SimpleBooleanProperty analyzed;
- private final SimpleObjectProperty category = new SimpleObjectProperty<>(null);
+ private final SimpleObjectProperty categoryTagName = new SimpleObjectProperty<>(null);
private String make;
private String model;
+ private final CategoryManager categoryManager;
+
protected DrawableFile(AbstractFile file, Boolean analyzed) {
this.analyzed = new SimpleBooleanProperty(analyzed);
this.file = file;
+
+ categoryManager = ImageGalleryController.getController(Case.getCurrentCase()).getCategoryManager();
}
public abstract boolean isVideo();
@@ -229,32 +233,30 @@ public abstract class DrawableFile {
return "";
}
- public void setCategory(DhsImageCategory category) {
- categoryProperty().set(category);
-
- }
-
- public DhsImageCategory getCategory() {
+ public TagName getCategory() {
updateCategory();
- return category.get();
+ return categoryTagName.get();
}
- public SimpleObjectProperty categoryProperty() {
- return category;
+ public SimpleObjectProperty categoryProperty() {
+ return categoryTagName;
}
/**
- * set the category property to the most severe one found
+ * Update the category property.
*/
private void updateCategory() {
try {
- category.set(getContentTags().stream()
- .map(Tag::getName).filter(CategoryManager::isCategoryTagName)
- .map(TagName::getDisplayName)
- .map(DhsImageCategory::fromDisplayName)
- .sorted().findFirst() //sort by severity and take the first
- .orElse(DhsImageCategory.ZERO)
- );
+ List contentTags = getContentTags();
+ TagName tag = null;
+ for (ContentTag ct : contentTags) {
+ TagName tagName = ct.getName();
+ if (categoryManager.isCategoryTagName(tagName)) {
+ tag = tagName;
+ break;
+ }
+ }
+ categoryTagName.set(tag);
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "problem looking up category for " + this.getContentPathSafe(), ex); //NON-NLS
} catch (IllegalStateException ex) {
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
index d2cdc484ce..ec06c6e343 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
@@ -20,10 +20,11 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Level;
-import java.util.stream.Collectors;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@@ -33,7 +34,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
@@ -54,10 +54,16 @@ public final class DrawableTagsManager {
private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
private final TagsManager autopsyTagsManager;
- /** The tag name corresponding to the "built-in" tag "Follow Up" */
+ /**
+ * The tag name corresponding to the "built-in" tag "Follow Up"
+ */
private final TagName followUpTagName;
private final TagName bookmarkTagName;
+ private final ImageGalleryController controller;
+
+ private final Comparator compareByDisplayName;
+
/**
* Used to distribute TagsChangeEvents
*/
@@ -74,6 +80,14 @@ public final class DrawableTagsManager {
this.autopsyTagsManager = controller.getCase().getServices().getTagsManager();
followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp());
bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark());
+ this.controller = controller;
+
+ compareByDisplayName = new Comparator() {
+ @Override
+ public int compare(TagName tagName1, TagName tagName2) {
+ return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
+ }
+ };
}
/**
@@ -129,25 +143,26 @@ public final class DrawableTagsManager {
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public List getNonCategoryTagNames() throws TskCoreException {
- return autopsyTagsManager.getAllTagNames().stream()
- .filter(CategoryManager::isNotCategoryTagName)
- .distinct().sorted()
- .collect(Collectors.toList());
+ List nonCategoryTagNames = new ArrayList<>();
+ List allTags = autopsyTagsManager.getAllTagNames();
+ for (TagName tag : allTags) {
+ if (controller.getCategoryManager().isNotCategoryTagName(tag)) {
+ nonCategoryTagNames.add(tag);
+ }
+ }
+ nonCategoryTagNames.sort(compareByDisplayName);
+ return nonCategoryTagNames;
}
/**
* Get all the TagNames that are categories
*
- * @return All the TagNames that are categories, in alphabetical order by
- * displayName.
+ * @return All the TagNames that are categories.
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public List getCategoryTagNames() throws TskCoreException {
- return autopsyTagsManager.getAllTagNames().stream()
- .filter(CategoryManager::isCategoryTagName)
- .distinct().sorted()
- .collect(Collectors.toList());
+ return controller.getCategoryManager().getCategorySet().getTagNames();
}
/**
@@ -190,15 +205,11 @@ public final class DrawableTagsManager {
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
if (returnTagName != null) {
return returnTagName;
- }
+ }
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
}
}
- public TagName getTagName(DhsImageCategory cat) throws TskCoreException {
- return getTagName(cat.getDisplayName());
- }
-
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java
index 5f6f72279e..f972e6cc9e 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java
@@ -55,7 +55,7 @@ public class VideoFile extends DrawableFile {
}
/**
- * Get the genereric video thumbnail.
+ * Get the generic video thumbnail.
*
* @return The thumbnail.
*/
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java
index 52dadf5ecd..e750c5ff2a 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java
@@ -59,6 +59,7 @@ public class GroupKey> implements Comparable
public String getValueDisplayName() {
return Objects.equals(attr, DrawableAttribute.TAGS)
+ || Objects.equals(attr, DrawableAttribute.CATEGORY)
? ((TagName) getValue()).getDisplayName()
: Objects.toString(getValue(), "unknown");
}
@@ -74,8 +75,9 @@ public class GroupKey> implements Comparable
hash = 79 * hash + Objects.hashCode(this.val);
hash = 79 * hash + Objects.hashCode(this.attr);
- if (this.dataSource != null)
- hash = 79 * hash + (int)this.dataSource.getId();
+ if (this.dataSource != null) {
+ hash = 79 * hash + (int) this.dataSource.getId();
+ }
return hash;
}
@@ -99,20 +101,20 @@ public class GroupKey> implements Comparable
if (!Objects.equals(this.attr, other.attr)) {
return false;
}
-
+
// Data source is significant only for PATH based groups.
if (this.attr == DrawableAttribute.PATH) {
if (this.dataSource != null && other.dataSource != null) {
- return this.dataSource.getId() == other.dataSource.getId();
+ return this.dataSource.getId() == other.dataSource.getId();
} else if (this.dataSource == null && other.dataSource == null) {
// neither group has a datasource
return true;
} else {
- // one group has a datasource, other doesn't
+ // one group has a datasource, other doesn't
return false;
}
}
-
+
return true;
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
index 59dae2af00..774f6ea3e5 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java
@@ -62,7 +62,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -70,9 +69,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
-import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@@ -106,23 +103,23 @@ public class GroupManager {
private final ImageGalleryController controller;
/**
- * Keeps track of the current path group
- * - a change in path indicates the current path group is analyzed
+ * Keeps track of the current path group - a change in path indicates the
+ * current path group is analyzed
*/
@GuardedBy("this") //NOPMD
private GroupKey> currentPathGroup = null;
-
+
/**
- * list of all analyzed groups - i.e. groups that are ready to be shown to user.
- * These are groups under the selected groupBy attribute.
+ * list of all analyzed groups - i.e. groups that are ready to be shown to
+ * user. These are groups under the selected groupBy attribute.
*/
@GuardedBy("this") //NOPMD
private final ObservableList analyzedGroups = FXCollections.observableArrayList();
private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
/**
- * list of unseen groups
- * These are groups under the selected groupBy attribute.
+ * list of unseen groups These are groups under the selected groupBy
+ * attribute.
*/
@GuardedBy("this") //NOPMD
private final ObservableList unSeenGroups = FXCollections.observableArrayList();
@@ -186,15 +183,15 @@ public class GroupManager {
@SuppressWarnings({"rawtypes", "unchecked"})
synchronized public Set> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException {
Set> resultSet = new HashSet<>();
-
- for (DrawableAttribute> attr: DrawableAttribute.getGroupableAttrs()) {
+
+ for (DrawableAttribute> attr : DrawableAttribute.getGroupableAttrs()) {
for (Comparable> val : attr.getValue(file)) {
if (attr == DrawableAttribute.PATH) {
resultSet.add(new GroupKey(attr, val, file.getDataSource()));
} else if (attr == DrawableAttribute.TAGS) {
//don't show groups for the categories when grouped by tags.
- if (CategoryManager.isNotCategoryTagName((TagName) val)) {
+ if (controller.getCategoryManager().isNotCategoryTagName((TagName) val)) {
resultSet.add(new GroupKey(attr, val, null));
}
} else {
@@ -204,9 +201,8 @@ public class GroupManager {
}
return resultSet;
}
-
- /**
+ /**
*
* Returns GroupKeys for all the Groups the given file is a part of.
*
@@ -225,7 +221,7 @@ public class GroupManager {
}
return Collections.emptySet();
}
-
+
/**
* @param groupKey
*
@@ -244,7 +240,7 @@ public class GroupManager {
setGroupBy(DrawableAttribute.PATH);
setSortOrder(SortOrder.ASCENDING);
setDataSource(null);
-
+
unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener);
unSeenGroups.clear();
analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener);
@@ -300,12 +296,12 @@ public class GroupManager {
public ListenableFuture> markGroupUnseen(DrawableGroup group) {
return exec.submit(() -> {
try {
-
+
getDrawableDB().markGroupUnseen(group.getGroupKey());
// only update and reshuffle if its new results
if (group.isSeen() == true) {
group.setSeen(false);
- }
+ }
// The group may already be in 'unseen' state, e.g. when new files are added,
// but not be on the unseenGroupsList yet.
updateUnSeenGroups(group);
@@ -314,7 +310,7 @@ public class GroupManager {
}
});
}
-
+
/**
* Update unseenGroups list accordingly based on the current status of
* 'group'. Removes it if it is seen or adds it if it is unseen.
@@ -322,13 +318,13 @@ public class GroupManager {
* @param group
*/
synchronized private void updateUnSeenGroups(DrawableGroup group) {
- if (group.isSeen()) {
- unSeenGroups.removeAll(group);
- } else if (unSeenGroups.contains(group) == false &&
- getGroupBy() == group.getGroupKey().getAttribute()) {
- unSeenGroups.add(group);
- }
- sortUnseenGroups();
+ if (group.isSeen()) {
+ unSeenGroups.removeAll(group);
+ } else if (unSeenGroups.contains(group) == false
+ && getGroupBy() == group.getGroupKey().getAttribute()) {
+ unSeenGroups.add(group);
+ }
+ sortUnseenGroups();
}
/**
@@ -390,7 +386,7 @@ public class GroupManager {
switch (groupKey.getAttribute().attrName) {
//these cases get special treatment
case CATEGORY:
- return getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
+ return getFileIDsWithCategory((TagName) groupKey.getValue());
case TAGS:
return getFileIDsWithTag((TagName) groupKey.getValue());
case MIME_TYPE:
@@ -405,33 +401,18 @@ public class GroupManager {
// @@@ This was kind of slow in the profiler. Maybe we should cache it.
// Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
- synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException {
+ synchronized public Set getFileIDsWithCategory(TagName category) throws TskCoreException {
Set fileIDsToReturn = Collections.emptySet();
try {
final DrawableTagsManager tagsManager = controller.getTagsManager();
- if (category == DhsImageCategory.ZERO) {
- Set fileIDs = new HashSet<>();
- for (TagName catTagName : tagsManager.getCategoryTagNames()) {
- if (notEqual(catTagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) {
- tagsManager.getContentTagsByTagName(catTagName).stream()
- .filter(ct -> ct.getContent() instanceof AbstractFile)
- .map(ct -> ct.getContent().getId())
- .filter(getDrawableDB()::isInDB)
- .forEach(fileIDs::add);
- }
- }
- fileIDsToReturn = getDrawableDB().findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS
- } else {
-
- List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category));
- fileIDsToReturn = contentTags.stream()
- .filter(ct -> ct.getContent() instanceof AbstractFile)
- .filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
- .map(ct -> ct.getContent().getId())
- .collect(Collectors.toSet());
- }
+ List contentTags = tagsManager.getContentTagsByTagName(category);
+ fileIDsToReturn = contentTags.stream()
+ .filter(ct -> ct.getContent() instanceof AbstractFile)
+ .filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
+ .map(ct -> ct.getContent().getId())
+ .collect(Collectors.toSet());
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS
throw ex;
@@ -552,14 +533,14 @@ public class GroupManager {
synchronized public void handleTagAdded(ContentTagAddedEvent evt) {
GroupKey> newGroupKey = null;
final long fileID = evt.getAddedTag().getContent().getId();
- if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) {
- newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource());
+ if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(evt.getAddedTag().getName())) {
+ newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, evt.getAddedTag().getName(), getDataSource());
for (GroupKey> oldGroupKey : groupMap.keySet()) {
if (oldGroupKey.equals(newGroupKey) == false) {
removeFromGroup(oldGroupKey, fileID);
}
}
- } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) {
+ } else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(evt.getAddedTag().getName())) {
newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource());
}
if (newGroupKey != null) {
@@ -569,7 +550,8 @@ public class GroupManager {
}
/**
- * Adds an analyzed file to the in-memory group data structures. Marks the group as unseen.
+ * Adds an analyzed file to the in-memory group data structures. Marks the
+ * group as unseen.
*
* @param group Group being added to (will be null if a group has not yet
* been created)
@@ -584,16 +566,20 @@ public class GroupManager {
//if there wasn't already a DrawableGroup, then check if this group is now
// in an appropriate state to get one made.
// Path group, for example, only gets a DrawableGroup created when all files are analyzed
- /* NOTE: With the current (Jan 2019) behavior of how we detect a PATH group as being analyzed, the group
- * is not marked as analyzed until we add a file for another folder. So, when the last picture in a folder
- * is added to the group, the call to 'populateIfAnalyzed' will still not return a group and therefore this
- * method will never mark the group as unseen. */
+ /*
+ * NOTE: With the current (Jan 2019) behavior of how we detect a
+ * PATH group as being analyzed, the group is not marked as analyzed
+ * until we add a file for another folder. So, when the last picture
+ * in a folder is added to the group, the call to
+ * 'populateIfAnalyzed' will still not return a group and therefore
+ * this method will never mark the group as unseen.
+ */
group = popuplateIfAnalyzed(groupKey, null);
} else {
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
group.addFile(fileID);
}
-
+
// reset the seen status for the group (if it is currently considered analyzed)
if (group != null) {
markGroupUnseen(group);
@@ -605,18 +591,14 @@ public class GroupManager {
GroupKey> groupKey = null;
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
final TagName deletedTagName = deletedTagInfo.getName();
- if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(deletedTagName)) {
- groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(deletedTagName), null);
- } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(deletedTagName)) {
+ if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(deletedTagName)) {
+ groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, deletedTagName, null);
+ } else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null);
}
if (groupKey != null) {
final long fileID = deletedTagInfo.getContentID();
DrawableGroup g = removeFromGroup(groupKey, fileID);
-
- if (controller.getCategoryManager().getTagName(DhsImageCategory.ZERO).equals(deletedTagName) == false) {
- addFileToGroup(null, new GroupKey<>(DrawableAttribute.CATEGORY, DhsImageCategory.ZERO, null), fileID);
- }
}
}
@@ -653,13 +635,13 @@ public class GroupManager {
try {
DrawableFile file = getDrawableDB().getFileFromID(fileId);
String pathVal = file.getDrawablePath();
- GroupKey> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource());
-
+ GroupKey> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH, pathVal, file.getDataSource());
+
updateCurrentPathGroup(pathGroupKey);
} catch (TskCoreException | TskDataException ex) {
logger.log(Level.WARNING, "Error getting drawabledb for fileId " + fileId, ex);
- }
-
+ }
+
// Update all the groups that this file belongs to
Set> groupsForFile = getAllGroupKeysForFile(fileId);
for (GroupKey> gk : groupsForFile) {
@@ -672,45 +654,45 @@ public class GroupManager {
//we fire this event for all files so that the category counts get updated during initial db population
controller.getCategoryManager().fireChange(updatedFileIDs, null);
}
-
+
/**
- * Checks if the given path is different from the current path group.
- * If so, updates the current path group as analyzed, and sets current path
- * group to the given path.
- *
- * The idea is that when the path of the files being processed changes,
- * we have moved from one folder to the next, and the group for the
- * previous PATH can be considered as analyzed and can be displayed.
- *
- * NOTE: this a close approximation for when all files in a folder have been processed,
- * but there's some room for error - files may go down the ingest pipleline
- * out of order or the events may not always arrive in the same order
- *
- * @param groupKey
+ * Checks if the given path is different from the current path group. If so,
+ * updates the current path group as analyzed, and sets current path group
+ * to the given path.
+ *
+ * The idea is that when the path of the files being processed changes, we
+ * have moved from one folder to the next, and the group for the previous
+ * PATH can be considered as analyzed and can be displayed.
+ *
+ * NOTE: this a close approximation for when all files in a folder have been
+ * processed, but there's some room for error - files may go down the ingest
+ * pipleline out of order or the events may not always arrive in the same
+ * order
+ *
+ * @param groupKey
*/
synchronized private void updateCurrentPathGroup(GroupKey> groupKey) {
try {
if (groupKey.getAttribute() == DrawableAttribute.PATH) {
-
+
if (this.currentPathGroup == null) {
currentPathGroup = groupKey;
- }
- else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
+ } else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
// mark the last path group as analyzed
getDrawableDB().markGroupAnalyzed(currentPathGroup);
popuplateIfAnalyzed(currentPathGroup, null);
-
+
currentPathGroup = groupKey;
}
}
- }
- catch (TskCoreException ex) {
+ } catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error setting is_analyzed status for group: %s", groupKey.getValue().toString()), ex); //NON-NLS
- }
+ }
}
/**
- * Resets current path group, after marking the current path group as analyzed.
+ * Resets current path group, after marking the current path group as
+ * analyzed.
*/
synchronized public void resetCurrentPathGroup() {
try {
@@ -719,11 +701,11 @@ public class GroupManager {
popuplateIfAnalyzed(currentPathGroup, null);
currentPathGroup = null;
}
- }
- catch (TskCoreException ex) {
+ } catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error resetting last path group: %s", currentPathGroup.getValue().toString()), ex); //NON-NLS
}
}
+
/**
* If the group is analyzed (or other criteria based on grouping) and should
* be shown to the user, then add it to the appropriate data structures so
@@ -768,12 +750,12 @@ public class GroupManager {
controller.getCategoryManager().registerListener(group);
groupMap.put(groupKey, group);
}
-
+
// Add to analyzedGroups only if it's the a group with the selected groupBy attribute
- if ((analyzedGroups.contains(group) == false) &&
- (getGroupBy() == group.getGroupKey().getAttribute())) {
- analyzedGroups.add(group);
- sortAnalyzedGroups();
+ if ((analyzedGroups.contains(group) == false)
+ && (getGroupBy() == group.getGroupKey().getAttribute())) {
+ analyzedGroups.add(group);
+ sortAnalyzedGroups();
}
updateUnSeenGroups(group);
@@ -944,11 +926,11 @@ public class GroupManager {
switch (groupBy.attrName) {
//these cases get special treatment
case CATEGORY:
- results.putAll(null, Arrays.asList(DhsImageCategory.values()));
+ results.putAll(null, controller.getCategoryManager().getCategories());
break;
case TAGS:
results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream()
- .filter(CategoryManager::isNotCategoryTagName)
+ .filter(controller.getCategoryManager()::isNotCategoryTagName)
.collect(Collectors.toList()));
break;
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java
index 97a75f0f5b..b187f57297 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java
@@ -63,9 +63,7 @@ public class GroupSortBy implements Comparator {
*/
public final static GroupSortBy PRIORITY
= new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
- Comparator.comparing(DrawableGroup::getHashHitDensity)
- .thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
- .reversed());
+ Comparator.comparing(DrawableGroup::getHashHitDensity).reversed());
private final static ObservableList values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java
index 8288ee4abf..f032698578 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java
@@ -56,7 +56,7 @@ public final class GuiUtils {
/**
* Create a MenuItem that performs the given action and also set the Action
- * as the action for the given Button. Usefull to have a SplitMenuButton
+ * as the action for the given Button. Useful to have a SplitMenuButton
* remember the last chosen menu item as its action.
*
* @param button
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java
index 4884f580b7..f297d2b1af 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java
@@ -34,10 +34,10 @@ import javafx.scene.layout.VBox;
import javafx.util.Pair;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent;
+import org.sleuthkit.datamodel.TagName;
/**
* Displays summary statistics (counts) for each group
@@ -45,13 +45,13 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChan
public class SummaryTablePane extends AnchorPane {
@FXML
- private TableColumn, String> catColumn;
+ private TableColumn, String> catColumn;
@FXML
- private TableColumn, Long> countColumn;
+ private TableColumn, Long> countColumn;
@FXML
- private TableView> tableView;
+ private TableView> tableView;
private final ImageGalleryController controller;
@@ -97,9 +97,9 @@ public class SummaryTablePane extends AnchorPane {
*/
@Subscribe
public void handleCategoryChanged(CategoryChangeEvent evt) {
- final ObservableList> data = FXCollections.observableArrayList();
+ final ObservableList> data = FXCollections.observableArrayList();
if (Case.isCaseOpen()) {
- for (DhsImageCategory cat : DhsImageCategory.values()) {
+ for (TagName cat : controller.getCategoryManager().getCategories()) {
data.add(new Pair<>(cat, controller.getCategoryManager().getCategoryCount(cat)));
}
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
index ebe8ae3698..f492b5f325 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
@@ -20,12 +20,9 @@ package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -64,7 +61,6 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_DELETED;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
-import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
@@ -220,13 +216,13 @@ public class Toolbar extends ToolBar {
});
initTagMenuButton();
- CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller);
+ CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(controller.getCategoryManager().getCategories().get(0), controller);
catGroupMenuButton.setOnAction(cat5GroupAction);
catGroupMenuButton.setText(cat5GroupAction.getText());
catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic());
catGroupMenuButton.showingProperty().addListener(showing -> {
if (catGroupMenuButton.isShowing()) {
- List