bug fixes

This commit is contained in:
Greg DiCristofaro 2020-08-24 11:26:24 -04:00
parent e0351c7c0a
commit 78d69f173b
13 changed files with 543 additions and 282 deletions

View File

@ -108,7 +108,24 @@ final class DataSourceInfoUtilities {
* obtained. * obtained.
*/ */
static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) { static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) {
try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { return getBaseQueryResult(SleuthkitCaseProvider.DEFAULT, query, processor, errorMessage);
}
/**
* Retrieves a result based on the provided query.
*
* @param provider The means of obtaining a SleuthkitCase.
* @param query The query.
* @param processor The result set handler.
* @param errorMessage The error message to display if there is an error
* retrieving the resultset.
*
* @return The ResultSetHandler value or null if no ResultSet could be
* obtained.
*/
static <T> T getBaseQueryResult(SleuthkitCaseProvider provider, String query, ResultSetHandler<T> processor, String errorMessage) {
try (SleuthkitCase.CaseDbQuery dbQuery = provider.get().executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet(); ResultSet resultSet = dbQuery.getResultSet();
try { try {
return processor.process(resultSet); return processor.process(resultSet);

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.datasourcesummary.datamodel;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.sleuthkit.datamodel.DataSource;
/**
* Provides summary information about top domains in a datasource.
*/
public class DataSourceTopDomainsSummary {
private static final long SLEEP_TIME = 5000;
// private final SleuthkitCaseProvider provider;
//
// public DataSourceTopDomainsSummary() {
// this(SleuthkitCaseProvider.DEFAULT);
// }
//
// public DataSourceTopDomainsSummary(SleuthkitCaseProvider provider) {
// this.provider = provider;
// }
interface Function2<A1,A2,O> {
O apply(A1 a1, A2 a2);
}
public List<TopDomainsResult> getRecentDomains(DataSource dataSource, int count) throws InterruptedException {
Thread.sleep(SLEEP_TIME);
final String dId = Long.toString(dataSource.getId());
final Function2<String, Integer, String> getId = (s,idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx);
return IntStream.range(0, count)
.mapToObj(num -> new TopDomainsResult(
getId.apply("domain", num),
getId.apply("url", num),
(long)num,
new Date(120, 1, num)
))
.collect(Collectors.toList());
}
}

View File

@ -66,6 +66,36 @@ public class DataSourceTopProgramsSummary {
*/ */
private static final String QUERY_SUFFIX = "_query"; private static final String QUERY_SUFFIX = "_query";
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
/** /**
* Creates a sql statement querying the blackboard attributes table for a * Creates a sql statement querying the blackboard attributes table for a
* particular attribute type and returning a specified value. That query * particular attribute type and returning a specified value. That query
@ -139,6 +169,17 @@ public class DataSourceTopProgramsSummary {
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'"; return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
} }
private final SleuthkitCaseProvider provider;
public DataSourceTopProgramsSummary() {
this(SleuthkitCaseProvider.DEFAULT);
}
public DataSourceTopProgramsSummary(SleuthkitCaseProvider provider) {
this.provider = provider;
}
/** /**
* Retrieves a list of the top programs used on the data source. Currently * Retrieves a list of the top programs used on the data source. Currently
* determines this based off of which prefetch results return the highest * determines this based off of which prefetch results return the highest
@ -149,7 +190,7 @@ public class DataSourceTopProgramsSummary {
* *
* @return * @return
*/ */
public static List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) { public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) {
if (dataSource == null || count <= 0) { if (dataSource == null || count <= 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -225,38 +266,9 @@ public class DataSourceTopProgramsSummary {
return progResults; return progResults;
}; };
return getBaseQueryResult(query, handler, errorMessage); return getBaseQueryResult(provider, query, handler, errorMessage);
} }
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
/** /**
* Determines a short folder name if any. Otherwise, returns empty string. * Determines a short folder name if any. Otherwise, returns empty string.
@ -289,7 +301,4 @@ public class DataSourceTopProgramsSummary {
return ""; return "";
} }
private DataSourceTopProgramsSummary() {
}
} }

View File

@ -0,0 +1,33 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* An interface to provide the current SleuthkitCase object. By default, this
* uses Case.getCurrentCaseThrows().getSleuthkkitCase().
*/
public interface SleuthkitCaseProvider {
public static final SleuthkitCaseProvider DEFAULT = () -> Case.getCurrentCaseThrows().getSleuthkitCase();
SleuthkitCase get() throws NoCurrentCaseException;
}

View File

@ -0,0 +1,57 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.datasourcesummary.datamodel;
import java.util.Date;
/**
* Describes a result of a program run on a datasource.
*/
public class TopDomainsResult {
private final String domain;
private final String url;
private final Long visitTimes;
private final Date lastVisit;
public TopDomainsResult(String domain, String url, Long visitTimes, Date lastVisit) {
this.domain = domain;
this.url = url;
this.visitTimes = visitTimes;
this.lastVisit = lastVisit;
}
public String getDomain() {
return domain;
}
public String getUrl() {
return url;
}
public Long getVisitTimes() {
return visitTimes;
}
public Date getLastVisit() {
return lastVisit;
}
}

View File

@ -35,4 +35,5 @@ DataSourceSummaryDetailsPanel.unallocatedSizeLabel.text=Unallocated Space:
DataSourceSummaryDetailsPanel.unallocatedSizeValue.text= DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs
DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains

View File

@ -75,8 +75,12 @@ DataSourceSummaryTabbedPane_countsTab_title=Counts
DataSourceSummaryTabbedPane_detailsTab_title=Details DataSourceSummaryTabbedPane_detailsTab_title=Details
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs
DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains
DataSourceSummaryUserActivityPanel_tab_title=User Activity DataSourceSummaryUserActivityPanel_tab_title=User Activity
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> <Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="null"/>
</Property>
</Properties>
<AuxValues> <AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -11,59 +16,180 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,-58,0,0,2,-42"/>
</AuxValues> </AuxValues>
<Layout> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<DimensionLayout dim="0"> <SubComponents>
<Group type="103" groupAlignment="0" attributes="0"> <Container class="javax.swing.JScrollPane" name="contentScrollPane">
<Group type="102" alignment="0" attributes="0"> <Properties>
<EmptySpace max="-2" attributes="0"/> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Group type="103" groupAlignment="0" attributes="0"> <Dimension value="null"/>
<Component id="programsRunLabel" min="-2" pref="155" max="-2" attributes="0"/> </Property>
<Component id="topProgramsScrollPane" min="-2" pref="460" max="-2" attributes="0"/> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
</Group> <Dimension value="null"/>
<EmptySpace pref="128" max="32767" attributes="0"/> </Property>
</Group> </Properties>
</Group> <AuxValues>
</DimensionLayout> <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<DimensionLayout dim="1"> <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<Group type="103" groupAlignment="0" attributes="0"> </AuxValues>
<Group type="102" alignment="0" attributes="0"> <Constraints>
<EmptySpace max="-2" attributes="0"/> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<Component id="programsRunLabel" min="-2" max="-2" attributes="0"/> <BorderConstraints direction="Center"/>
<EmptySpace max="-2" attributes="0"/> </Constraint>
<Component id="topProgramsScrollPane" min="-2" max="-2" attributes="0"/> </Constraints>
<EmptySpace max="32767" attributes="0"/>
</Group> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Group> <SubComponents>
</DimensionLayout> <Container class="javax.swing.JPanel" name="contentPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="10" left="10" right="10" top="10"/>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[720, 450]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[720, 450]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="3"/>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Component class="javax.swing.JLabel" name="programsRunLabel"> <Component class="javax.swing.JLabel" name="programsRunLabel">
<Properties> <Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="DataSourceSummaryUserActivityPanel.programsRunLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="DataSourceSummaryUserActivityPanel.programsRunLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
<Property name="alignmentX" type="float" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Component.LEFT_ALIGNMENT" type="code"/>
</Property>
</Properties> </Properties>
<AuxValues> <AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues> </AuxValues>
</Component> </Component>
<Container class="javax.swing.JScrollPane" name="topProgramsScrollPane"> <Component class="javax.swing.Box$Filler" name="filler1">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[750, 187]"/> <Dimension value="[0, 2]"/>
</Property> </Property>
</Properties> </Properties>
<AuxValues> <AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/> <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="topProgramsTablePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="topProgramsTable"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
<SubComponents> </Container>
<Component class="javax.swing.JTable" name="topProgramsTable"> <Component class="javax.swing.Box$Filler" name="filler3">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component> </Component>
<Component class="javax.swing.JLabel" name="recentDomainsLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="DataSourceSummaryUserActivityPanel.recentDomainsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler2">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="recentDomainsTablePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="recentDomainsTable"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>

View File

@ -21,20 +21,26 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.awt.Component; import java.awt.Component;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.swing.JLabel; import java.util.stream.Collectors;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopDomainsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopProgramsSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsResult;
import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker;
import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.guiutils.internal.DataLoadingResult;
import org.sleuthkit.autopsy.guiutils.internal.DataResultJTable;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultCellModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultColumnModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.HorizontalAlign;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.autopsy.guiutils.internal.SwingWorkerSequentialRunner;
/** /**
* A panel to display user activity. * A panel to display user activity.
@ -44,18 +50,21 @@ import org.sleuthkit.datamodel.DataSource;
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program", "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder", "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times", "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run" "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run",
}) "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain",
"DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL",
"DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access",})
public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel { public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault());
private static final int TOP_PROGS_COUNT = 10; private static final int TOP_PROGS_COUNT = 10;
private static final DefaultTableCellRenderer RIGHT_ALIGNED_RENDERER = new DefaultTableCellRenderer(); private static final int TOP_DOMAINS_COUNT = 10;
static { private final SwingWorkerSequentialRunner loader = new SwingWorkerSequentialRunner();
RIGHT_ALIGNED_RENDERER.setHorizontalAlignment(JLabel.RIGHT); private final DataResultJTable<TopProgramsResult> topProgramsTable;
} private final DataResultJTable<TopDomainsResult> recentDomainsTable;
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private DataSource dataSource; private DataSource dataSource;
@ -63,8 +72,61 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
* Creates new form DataSourceUserActivityPanel * Creates new form DataSourceUserActivityPanel
*/ */
public DataSourceSummaryUserActivityPanel() { public DataSourceSummaryUserActivityPanel() {
this(new DataSourceTopProgramsSummary(), new DataSourceTopDomainsSummary());
}
public DataSourceSummaryUserActivityPanel(DataSourceTopProgramsSummary topProgramsData, DataSourceTopDomainsSummary topDomainsData) {
// set up recent programs table
this.topProgramsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList(
new DefaultColumnModel<TopProgramsResult>(
(prog) -> new DefaultCellModel(prog.getProgramName())
.setTooltip(prog.getProgramPath()),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header())
.setWidth(250),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(),
(prog) -> topProgramsData.getShortFolderName(prog.getProgramPath(), prog.getProgramName()))
.setWidth(150),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(),
(prog) -> prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()))
.setWidth(80)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header(),
(prog) -> prog.getLastRun() == null ? "" : DATETIME_FORMAT.format(prog.getLastRun()))
.setWidth(150)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT)
)));
// set up recent domains table
recentDomainsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList(
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header(),
(d) -> d.getDomain())
.setWidth(250),
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header(),
(d) -> d.getUrl())
.setWidth(250),
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header(),
(d) -> DATETIME_FORMAT.format(d.getLastVisit()))
.setWidth(150)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT)
)));
// set up acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
topProgramsTable::setResult),
new DataFetchComponents<DataSource, List<TopDomainsResult>>(
(dataSource) -> topDomainsData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT),
recentDomainsTable::setResult)
);
initComponents(); initComponents();
topProgramsTable.getTableHeader().setReorderingAllowed(false);
} }
/** /**
@ -81,170 +143,25 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
* *
* @param dataSource The datasource to use in this panel. * @param dataSource The datasource to use in this panel.
*/ */
public void setDataSource(DataSource dataSource) { public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
if (dataSource == null || !Case.isCaseOpen()) { if (dataSource == null || !Case.isCaseOpen()) {
updateTopPrograms(new TopProgramsModel(null)); dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataLoadingResult.getLoaded(null)));
} else { } else {
updateTopPrograms(getTopProgramsModel(dataSource)); dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataLoadingResult.getLoading()));
List<DataFetchWorker<?, ?>> workers = dataFetchComponents
.stream()
.map((components) -> new DataFetchWorker<>(components, dataSource))
.collect(Collectors.toList());
loader.resetLoad(workers);
} }
} }
/**
* Updates the Top Programs Table in the gui.
*
* @param data The data in Object[][] form to be used by the
* DefaultTableModel.
*/
private void updateTopPrograms(TopProgramsModel model) {
topProgramsTable.setModel(model);
topProgramsTable.getColumnModel().getColumn(0).setPreferredWidth(250);
topProgramsTable.getColumnModel().getColumn(0).setCellRenderer(PATH_CELL_RENDERER);
topProgramsTable.getColumnModel().getColumn(1).setPreferredWidth(150);
topProgramsTable.getColumnModel().getColumn(2).setCellRenderer(RIGHT_ALIGNED_RENDERER);
topProgramsTable.getColumnModel().getColumn(2).setPreferredWidth(80);
topProgramsTable.getColumnModel().getColumn(3).setPreferredWidth(150);
topProgramsScrollPane.getVerticalScrollBar().setValue(0);
this.repaint();
}
/**
* The counts of top programs run.
*
* @param selectedDataSource The DataSource.
*
* @return The JTable data model of counts of program runs.
*/
private static TopProgramsModel getTopProgramsModel(DataSource selectedDataSource) {
List<TopProgramsResult> topProgramList
= DataSourceTopProgramsSummary.getTopPrograms(selectedDataSource, TOP_PROGS_COUNT);
if (topProgramList == null) {
return new TopProgramsModel(null);
} else {
return new TopProgramsModel(topProgramList);
}
}
/**
* A POJO defining the values present in the name cell. Defines the name as
* well as the path for the tooltip.
*/
private static class ProgramNameCellValue {
private final String programName;
private final String programPath;
ProgramNameCellValue(String programName, String programPath) {
this.programName = programName;
this.programPath = programPath;
}
@Override
public String toString() {
// override so that the value in the cell reads as programName
return programName;
}
/**
* @return The program name.
*/
String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
String getProgramPath() {
return programPath;
}
}
/**
* Defines a cell renderer for the first cell rendering the name as the text
* and path as the tooltip.
*/
private static TableCellRenderer PATH_CELL_RENDERER = new DefaultTableCellRenderer() {
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
JLabel c = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value instanceof ProgramNameCellValue) {
ProgramNameCellValue cellValue = (ProgramNameCellValue) value;
c.setToolTipText(cellValue.getProgramPath());
}
return c;
}
};
/**
* Defines the table model for a JTable of the programs. Accepts a list of
* TopProgramsResult objects as rows data source.
*/
private static class TopProgramsModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
// column headers for artifact counts table
private static final String[] TOP_PROGS_COLUMN_HEADERS = new String[]{
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header()
};
private final List<TopProgramsResult> programResults;
/**
* Main constructor.
*
* @param programResults The results to display.
*/
TopProgramsModel(List<TopProgramsResult> programResults) {
this.programResults = programResults == null ? new ArrayList<>() : Collections.unmodifiableList(programResults);
}
@Override
public String getColumnName(int column) {
return column < 0 || column >= TOP_PROGS_COLUMN_HEADERS.length ? null : TOP_PROGS_COLUMN_HEADERS[column];
}
@Override
public int getRowCount() {
return programResults.size();
}
@Override
public int getColumnCount() {
return TOP_PROGS_COLUMN_HEADERS.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= programResults.size()) {
return null;
}
TopProgramsResult result = programResults.get(rowIndex);
switch (columnIndex) {
case 0:
return new ProgramNameCellValue(result.getProgramName(), result.getProgramPath());
case 1:
return DataSourceTopProgramsSummary.getShortFolderName(result.getProgramPath(), result.getProgramName());
case 2:
return result.getRunTimes();
case 3:
return result.getLastRun() == null ? null : DATETIME_FORMAT.format(result.getLastRun());
default:
return null;
}
}
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always * WARNING: Do NOT modify this code. The content of this method is always
@ -254,40 +171,57 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
javax.swing.JScrollPane contentScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel contentPanel = new javax.swing.JPanel();
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel(); javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
topProgramsScrollPane = new javax.swing.JScrollPane(); javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
topProgramsTable = new javax.swing.JTable(); javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable;
setMaximumSize(null);
setLayout(new java.awt.BorderLayout());
contentScrollPane.setMaximumSize(null);
contentScrollPane.setMinimumSize(null);
contentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
contentPanel.setMaximumSize(new java.awt.Dimension(720, 450));
contentPanel.setMinimumSize(new java.awt.Dimension(720, 450));
contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS));
programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N
programsRunLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
contentPanel.add(programsRunLabel);
contentPanel.add(filler1);
topProgramsScrollPane.setPreferredSize(new java.awt.Dimension(750, 187)); topProgramsTablePanel.setAlignmentX(0.0F);
topProgramsScrollPane.setViewportView(topProgramsTable); topProgramsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187));
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187));
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187));
contentPanel.add(topProgramsTablePanel);
contentPanel.add(filler3);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
this.setLayout(layout); org.openide.awt.Mnemonics.setLocalizedText(recentDomainsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.recentDomainsLabel.text")); // NOI18N
layout.setHorizontalGroup( contentPanel.add(recentDomainsLabel);
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) contentPanel.add(filler2);
.addGroup(layout.createSequentialGroup()
.addContainerGap() recentDomainsTablePanel.setAlignmentX(0.0F);
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) recentDomainsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187));
.addComponent(programsRunLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187));
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 460, javax.swing.GroupLayout.PREFERRED_SIZE)) recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187));
.addContainerGap(128, Short.MAX_VALUE)) contentPanel.add(recentDomainsTablePanel);
);
layout.setVerticalGroup( contentScrollPane.setViewportView(contentPanel);
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() add(contentScrollPane, java.awt.BorderLayout.CENTER);
.addContainerGap()
.addComponent(programsRunLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane topProgramsScrollPane;
private javax.swing.JTable topProgramsTable;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -0,0 +1,2 @@
DataResultJTable_errorMessage_defaultText=There was an error loading results.
DataResultJTable_loadingMessage_defaultText=Loading results...

View File

@ -121,24 +121,35 @@ public class DataFetchWorker<A, R> extends SwingWorker<R, Void> {
@Override @Override
protected void done() { protected void done() {
// if cancelled, simply return
if (Thread.interrupted() || isCancelled()) {
return;
}
R result = null; R result = null;
try { try {
result = get(); result = get();
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
// if cancelled, set not loaded andt return // if cancelled, simply return
resultHandler.accept(DataLoadingResult.getNotLoaded());
return; return;
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
logger.log(Level.WARNING, "There was an error while fetching results.", ex);
Throwable inner = ex.getCause(); Throwable inner = ex.getCause();
// if cancelled during operation, simply return
if (inner != null && inner instanceof InterruptedException) {
return;
}
// otherwise, there is an error to log
logger.log(Level.WARNING, "There was an error while fetching results.", ex);
if (inner != null && inner instanceof DataProcessorException) { if (inner != null && inner instanceof DataProcessorException) {
resultHandler.accept(DataLoadingResult.getLoadError((DataProcessorException) inner)); resultHandler.accept(DataLoadingResult.getLoadError((DataProcessorException) inner));
} }
return; return;
} }
// if cancelled, simply return
if (Thread.interrupted() || isCancelled()) { if (Thread.interrupted() || isCancelled()) {
resultHandler.accept(DataLoadingResult.getNotLoaded());
return; return;
} }

View File

@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.guiutils.internal;
/** /**
* The intermediate or end result of a loading process. * The intermediate or end result of a loading process.
*/ */
class DataLoadingResult<R> { public class DataLoadingResult<R> {
// The state of loading in the result. // The state of loading in the result.
public enum ProcessorState { public enum ProcessorState {

View File

@ -243,7 +243,7 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
TableColumn col = new TableColumn(i); TableColumn col = new TableColumn(i);
ColumnModel<T> model = columns.get(i); ColumnModel<T> model = columns.get(i);
if (model.getWidth() != null && model.getWidth() >= 0) { if (model.getWidth() != null && model.getWidth() >= 0) {
col.setWidth(model.getWidth()); col.setPreferredWidth(model.getWidth());
} }
col.setHeaderValue(model.getTitle()); col.setHeaderValue(model.getTitle());
@ -278,11 +278,15 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
String text = cellModel.getText(); String text = cellModel.getText();
if (StringUtils.isNotBlank(text)) { if (StringUtils.isNotBlank(text)) {
defaultCell.setText(text); defaultCell.setText(text);
} else {
defaultCell.setText(null);
} }
String tooltip = cellModel.getTooltip(); String tooltip = cellModel.getTooltip();
if (StringUtils.isNotBlank(tooltip)) { if (StringUtils.isNotBlank(tooltip)) {
defaultCell.setToolTipText(tooltip); defaultCell.setToolTipText(tooltip);
} else {
defaultCell.setToolTipText(null);
} }
if (columnModel.getCellHorizontalAlignment() != null) { if (columnModel.getCellHorizontalAlignment() != null) {
@ -297,6 +301,8 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
defaultCell.setHorizontalAlignment(JLabel.RIGHT); defaultCell.setHorizontalAlignment(JLabel.RIGHT);
break; break;
} }
} else {
defaultCell.setHorizontalAlignment(JLabel.LEFT);
} }
return defaultCell; return defaultCell;