diff --git a/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml b/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml
index 0d2c942fd7..01c31c5d7a 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml
+++ b/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml
@@ -727,6 +727,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png b/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png
new file mode 100644
index 0000000000..907f05ea14
Binary files /dev/null and b/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png differ
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml b/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml
index 0cd7d48f2d..c4097961a8 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml
+++ b/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml
@@ -145,6 +145,11 @@
+
+
+
+
+
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml b/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml
index bd2dd2abf5..cd95d64bbb 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml
+++ b/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml
@@ -318,6 +318,10 @@
class="org.openjdk.jmc.flightrecorder.ui.pages.TlabPage$TlabPageFactory"
id="org.openjdk.jmc.flightrecorder.ui.tlab">
+
+
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java
index e57c125b6b..ea6dca51c9 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java
+++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java
@@ -132,6 +132,7 @@ public class ImageConstants {
public static final String PAGE_COMPILATIONS = "pages/compilations.png"; //$NON-NLS-1$
public static final String PAGE_CLASSLOADING = "pages/classloading.png"; //$NON-NLS-1$
public static final String PAGE_TLAB_ALLOCATIONS = "pages/allocations.png"; //$NON-NLS-1$
+ public static final String PAGE_SECURITY = "pages/security16.png"; //$NON-NLS-1$
public static final String PAGE_ENVIRONMENT = "pages/system.png"; //$NON-NLS-1$
public static final String PAGE_PROCESSES = "pages/processes.png"; //$NON-NLS-1$
public static final String PAGE_ENVIRONMENT_VARIABLES = "pages/environment.png"; //$NON-NLS-1$
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java
index 223e581d7d..ef3d883bc2 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java
+++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java
@@ -41,6 +41,7 @@
import java.util.stream.Stream;
import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
@@ -83,6 +84,21 @@ public void addColumn(IAttribute> a) {
a.getContentType() instanceof LinearKindOfQuantity, accessor);
}
+ public void addColumn(IAttribute> a, ColumnLabelProvider iconProvider) {
+ @SuppressWarnings("deprecation")
+ IMemberAccessor, IItem> accessor = ItemToolkit.accessor(a);
+ addColumn(a.getIdentifier(), a.getName(), a.getDescription(),
+ a.getContentType() instanceof LinearKindOfQuantity, accessor, iconProvider);
+ }
+
+ private void addColumn(
+ String columnId, String name, String description, boolean right, IMemberAccessor, IItem> accessor,
+ ColumnLabelProvider iconProvider) {
+ columns.add(new ColumnBuilder(name, columnId, accessor, iconProvider).description(description)
+ .style(right ? SWT.RIGHT : SWT.NONE).build());
+
+ }
+
public void addColumn(
String columnId, String name, String description, boolean right, IMemberAccessor, IItem> accessor) {
columns.add(new ColumnBuilder(name, columnId, accessor).description(description)
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java
index eb4fd826f3..f17fc13242 100644
--- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java
+++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java
@@ -523,6 +523,11 @@ public class Messages extends NLS {
public static String SAVE_AS_NO_SRC_ERROR_MSG;
public static String SAVE_AS_TITLE;
public static String SEARCH_TREE_TEXT;
+ public static String SecurityPage_PAGE_NAME;
+ public static String SecurityPage_TIMELINE_SELECTION;
+ public static String SecurityPage_TABLE_SELECTION;
+ public static String SecurityPage_SECTION_X509_CERTIFICATES;
+ public static String SecurityPage_SECTION_X509_ALGORITHMS;
public static String SELECTION_STORE_NO_SELECTION;
public static String SELECT_RANGE_WIZARD_DESCRIPTION;
public static String SELECT_RANGE_WIZARD_TEXT;
diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java
new file mode 100644
index 0000000000..1f7b33a20a
--- /dev/null
+++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The contents of this file are subject to the terms of either the Universal Permissive License
+ * v 1.0 as shown at https://oss.oracle.com/licenses/upl
+ *
+ * or the following license:
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided with
+ * the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openjdk.jmc.flightrecorder.ui.pages;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.widgets.Form;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.openjdk.jmc.common.IDescribable;
+import org.openjdk.jmc.common.IState;
+import org.openjdk.jmc.common.IWritableState;
+import org.openjdk.jmc.common.item.IAggregator;
+import org.openjdk.jmc.common.item.IItem;
+import org.openjdk.jmc.common.item.IItemCollection;
+import org.openjdk.jmc.common.item.IItemFilter;
+import org.openjdk.jmc.common.item.IMemberAccessor;
+import org.openjdk.jmc.common.item.ItemCollectionToolkit;
+import org.openjdk.jmc.common.item.ItemFilters;
+import org.openjdk.jmc.common.unit.IQuantity;
+import org.openjdk.jmc.common.unit.IRange;
+import org.openjdk.jmc.flightrecorder.JfrAttributes;
+import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators;
+import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes;
+import org.openjdk.jmc.flightrecorder.jdk.JdkFilters;
+import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs;
+import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics;
+import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI;
+import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory;
+import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage;
+import org.openjdk.jmc.flightrecorder.ui.IPageContainer;
+import org.openjdk.jmc.flightrecorder.ui.IPageDefinition;
+import org.openjdk.jmc.flightrecorder.ui.IPageUI;
+import org.openjdk.jmc.flightrecorder.ui.StreamModel;
+import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage;
+import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit;
+import org.openjdk.jmc.flightrecorder.ui.common.FilterComponent;
+import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector;
+import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState;
+import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants;
+import org.openjdk.jmc.flightrecorder.ui.common.ItemList;
+import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder;
+import org.openjdk.jmc.flightrecorder.ui.common.ItemRow;
+import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
+import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit;
+import org.openjdk.jmc.ui.UIPlugin;
+import org.openjdk.jmc.ui.charts.IXDataRenderer;
+import org.openjdk.jmc.ui.charts.RendererToolkit;
+import org.openjdk.jmc.ui.charts.XYChart;
+import org.openjdk.jmc.ui.column.ColumnManager.SelectionState;
+import org.openjdk.jmc.ui.column.ColumnMenusFactory;
+import org.openjdk.jmc.ui.column.TableSettings;
+import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
+import org.openjdk.jmc.ui.misc.ChartCanvas;
+import org.openjdk.jmc.ui.misc.CompositeToolkit;
+import org.openjdk.jmc.ui.misc.PersistableSashForm;
+import org.openjdk.jmc.ui.misc.SWTColorToolkit;
+
+public class SecurityPage extends AbstractDataPage {
+
+ public static class SecurityPageFactory implements IDataPageFactory {
+
+ @Override
+ public String getName(IState state) {
+ return Messages.SecurityPage_PAGE_NAME;
+ }
+
+ @Override
+ public ImageDescriptor getImageDescriptor(IState state) {
+ return FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.PAGE_SECURITY);
+ }
+
+ @Override
+ public String[] getTopics(IState state) {
+ return new String[] {JfrRuleTopics.SECURITY};
+ }
+
+ @Override
+ public IDisplayablePage createPage(IPageDefinition definition, StreamModel items, IPageContainer editor) {
+ return new SecurityPage(definition, items, editor);
+ }
+
+ }
+
+ private static ColumnLabelProvider LEGEND_LP = new ColumnLabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+ return getText(element, IDescribable::getName);
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ return getText(element, IDescribable::getDescription);
+ };
+
+ private String getText(Object element, Function accessor) {
+ return accessor.apply(JdkAggregators.X509_CERTIFICATE_COUNT);
+ };
+
+ @Override
+ public Image getImage(Object element) {
+ return SWTColorToolkit
+ .getColorThumbnail(SWTColorToolkit.asRGB(DataPageToolkit.getFieldColor((String) element)));
+
+ };
+ };
+
+ private static ColumnLabelProvider ICON_LP = new ColumnLabelProvider() {
+ @Override
+ public String getText(Object element) {
+ return ""; // Return empty string for image-only column
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof IItem) {
+ IItem iItem = (IItem) element;
+ IMemberAccessor extends String, IItem> iconAccessor = (IMemberAccessor extends String, IItem>) JdkAttributes.CRYPTO_ICON
+ .getAccessor(iItem.getType());
+
+ ImageDescriptor descriptor;
+ if (iconAccessor != null) {
+ String iconText = iconAccessor.getMember(iItem);
+ if (iconText.contains("ACTION"))
+ descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_ACTION);
+ else if (iconText.contains("ATTENTION"))
+ descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_ATTENTION);
+ else
+ descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_OK);
+
+ if (descriptor != null) {
+ return descriptor.createImage();
+ }
+ }
+ }
+ return null;
+ }
+ };
+
+ private static final ItemListBuilder SECURITY_X509_ALGORITHM_LIST = new ItemListBuilder();
+
+ static {
+
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CRYPTO_ICON, ICON_LP);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CRYPTO_REMARK);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SIGNATURE_ALGORITHM);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.KEY_TYPE);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.KEY_LENGTH);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SERIAL_NUMBER);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SUBJECT);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.VALID_FROM);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.VALID_UNTIL);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JfrAttributes.EVENT_THREAD);
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ISSUER);
+
+ }
+
+ private static final String SASH = "sash"; //$NON-NLS-1$
+ private static final String SECURITY_X509_ALGORITHM_TABLE = "securityX509AlgorithmTable"; //$NON-NLS-1$
+ private static final String SECURITY_X509_ALGORITHM_FILTER = "securityX509AlgorithmFilter"; //$NON-NLS-1$
+ private static final String CHART = "chart"; //$NON-NLS-1$
+ private static final String SERIES = "series"; //$NON-NLS-1$
+ private static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$
+ private static final String X509_CERTIFICATE_COUNT = "x509CertificateCount"; //$NON-NLS-1$
+
+ private class SecurityPageUI implements IPageUI {
+
+ private final SashForm sash;
+ private final IPageContainer pageContainer;
+ private final CheckboxTableViewer chartLegend;
+ private final ChartCanvas chartCanvas;
+ private final ItemList securityX509AlgorithmTable;
+ private final FilterComponent securityX509AlgorithmFilter;
+ private IItemCollection selectionItems;
+ private XYChart chart;
+ private IRange currentRange;
+ private FlavorSelector flavorSelector;
+
+ SecurityPageUI(Composite parent, FormToolkit toolkit, IPageContainer pageContainer, IState state) {
+ this.pageContainer = pageContainer;
+ Form form = DataPageToolkit.createForm(parent, toolkit, getName(), getIcon());
+ sash = new SashForm(form.getBody(), SWT.VERTICAL);
+ toolkit.adapt(sash);
+
+ Boolean isCertificateIdQty = getDataSource().getItems().getAggregate(JdkAggregators.IS_CERTIFICATE_ID_QTY);
+
+ if (isCertificateIdQty)
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ID_QTY);
+ else
+ SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ID);
+
+ //Algorithm table
+ Section s2 = CompositeToolkit.createSection(sash, toolkit, Messages.SecurityPage_SECTION_X509_ALGORITHMS);
+ securityX509AlgorithmTable = SECURITY_X509_ALGORITHM_LIST.buildWithoutBorder(s2,
+ TableSettings.forState(state.getChild(SECURITY_X509_ALGORITHM_TABLE)));
+
+ securityX509AlgorithmTable.getManager().getViewer().addSelectionChangedListener(e -> pageContainer
+ .showSelection(ItemCollectionToolkit.build(securityX509AlgorithmTable.getSelection().get())));
+ securityX509AlgorithmFilter = FilterComponent.createFilterComponent(securityX509AlgorithmTable,
+ securityX509AlgorithmFilterState, getDataSource().getItems().apply(JdkFilters.SECURITY),
+ pageContainer.getSelectionStore()::getSelections, this::onAlgorithmFilterChange);
+ MCContextMenuManager mm1 = MCContextMenuManager
+ .create(securityX509AlgorithmTable.getManager().getViewer().getControl());
+ ColumnMenusFactory.addDefaultMenus(securityX509AlgorithmTable.getManager(), mm1);
+ SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(),
+ securityX509AlgorithmTable, Messages.SecurityPage_TABLE_SELECTION, mm1);
+ mm1.add(securityX509AlgorithmFilter.getShowFilterAction());
+ mm1.add(securityX509AlgorithmFilter.getShowSearchAction());
+ securityX509AlgorithmFilter.loadState(state.getChild(SECURITY_X509_ALGORITHM_FILTER));
+ s2.setClient(securityX509AlgorithmFilter.getComponent());
+
+ PersistableSashForm.loadState(sash, state.getChild(SASH));
+ flavorSelector = FlavorSelector.itemsWithTimerange(form, JdkFilters.SECURITY, getDataSource().getItems(),
+ pageContainer, this::onInputSelected, this::onShow, flavorSelectorState);
+ addResultActions(form);
+ onAlgorithmFilterChange(securityX509AlgorithmFilterState);
+ securityX509AlgorithmTable.getManager().setSelectionState(securityX509AlgorithmSelectionState);
+
+ //Create Chart
+ Composite chartContainer = toolkit.createComposite(sash);
+ chartContainer.setLayout(new GridLayout(2, false));
+ chartCanvas = new ChartCanvas(chartContainer);
+ chart = new XYChart(pageContainer.getRecordingRange(), RendererToolkit.empty(), 120);
+ chart.setVisibleRange(timelineRange.getStart(), timelineRange.getEnd());
+ chart.addVisibleRangeListener(r -> timelineRange = r);
+ chartCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ DataPageToolkit.setChart(chartCanvas, chart, pageContainer::showSelection);
+ SelectionStoreActionToolkit.addSelectionStoreRangeActions(pageContainer.getSelectionStore(), chart,
+ JfrAttributes.LIFETIME, Messages.SecurityPage_TIMELINE_SELECTION, chartCanvas.getContextMenu());
+ DataPageToolkit.createChartTimestampTooltip(chartCanvas);
+
+ chartLegend = CheckboxTableViewer.newCheckList(chartContainer, SWT.BORDER);
+ GridData gd = new GridData(SWT.FILL, SWT.FILL, false, true);
+ gd.widthHint = 120;
+ chartLegend.getTable().setLayoutData(gd);
+ chartLegend.setContentProvider(ArrayContentProvider.getInstance());
+ chartLegend.setLabelProvider(LEGEND_LP);
+ chartLegend.addCheckStateListener(e -> buildChart());
+ chartLegend.addSelectionChangedListener(e -> buildChart());
+ ColumnViewerToolTipSupport.enableFor(chartLegend);
+ List