diff --git a/app/display/model/.classpath b/app/display/model/.classpath
index f1cf62ee2b..17d4dbfa63 100644
--- a/app/display/model/.classpath
+++ b/app/display/model/.classpath
@@ -1,12 +1,61 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/display/model/.project b/app/display/model/.project
index db0871a317..ddce5fcf23 100644
--- a/app/display/model/.project
+++ b/app/display/model/.project
@@ -10,8 +10,25 @@
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+ org.eclipse.m2e.core.maven2Nature
org.eclipse.jdt.core.javanature
+
+
+ 1775148788479
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/BaseWidgetsService.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/BaseWidgetsService.java
index e3d3c54f84..636411388c 100644
--- a/app/display/model/src/main/java/org/csstudio/display/builder/model/BaseWidgetsService.java
+++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/BaseWidgetsService.java
@@ -34,6 +34,7 @@
import org.csstudio.display.builder.model.widgets.PolygonWidget;
import org.csstudio.display.builder.model.widgets.PolylineWidget;
import org.csstudio.display.builder.model.widgets.ProgressBarWidget;
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget;
import org.csstudio.display.builder.model.widgets.RadioWidget;
import org.csstudio.display.builder.model.widgets.RectangleWidget;
import org.csstudio.display.builder.model.widgets.ScaledSliderWidget;
@@ -90,6 +91,7 @@ public Collection getWidgetDescriptors()
PolygonWidget.WIDGET_DESCRIPTOR,
PolylineWidget.WIDGET_DESCRIPTOR,
ProgressBarWidget.WIDGET_DESCRIPTOR,
+ PVASecurityWidget.WIDGET_DESCRIPTOR,
RadioWidget.WIDGET_DESCRIPTOR,
RectangleWidget.WIDGET_DESCRIPTOR,
ScaledSliderWidget.WIDGET_DESCRIPTOR,
diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/PVASecurityWidget.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/PVASecurityWidget.java
new file mode 100644
index 0000000000..b142499ac5
--- /dev/null
+++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/widgets/PVASecurityWidget.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Oak Ridge National Laboratory.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.csstudio.display.builder.model.widgets;
+
+import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.newBooleanPropertyDescriptor;
+import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.newStringPropertyDescriptor;
+
+import java.util.List;
+
+import org.csstudio.display.builder.model.Widget;
+import org.csstudio.display.builder.model.WidgetCategory;
+import org.csstudio.display.builder.model.WidgetDescriptor;
+import org.csstudio.display.builder.model.WidgetProperty;
+import org.csstudio.display.builder.model.WidgetPropertyCategory;
+import org.csstudio.display.builder.model.WidgetPropertyDescriptor;
+import org.csstudio.display.builder.model.properties.EnumWidgetProperty;
+
+@SuppressWarnings("nls")
+public class PVASecurityWidget extends PVWidget
+{
+ public enum PVASecurityMode
+ {
+ TLS_STATUS,
+ SERVER_IDENTITY,
+ CLIENT_IDENTITY,
+ AUTH_METHOD
+ }
+
+ public static final WidgetDescriptor WIDGET_DESCRIPTOR =
+ new WidgetDescriptor("pva_security", WidgetCategory.MONITOR,
+ "PVA Security",
+ "/icons/led.png",
+ "PVAccess TLS security status and identity")
+ {
+ @Override
+ public Widget createWidget()
+ {
+ return new PVASecurityWidget();
+ }
+ };
+
+ public static final WidgetPropertyDescriptor propDisplayMode =
+ new WidgetPropertyDescriptor<>(WidgetPropertyCategory.DISPLAY, "display_mode", "Display Mode")
+ {
+ @Override
+ public EnumWidgetProperty createProperty(final Widget widget, final PVASecurityMode default_value)
+ {
+ return new EnumWidgetProperty<>(this, widget, default_value);
+ }
+ };
+
+ public static final WidgetPropertyDescriptor propTLSActive =
+ newBooleanPropertyDescriptor(WidgetPropertyCategory.RUNTIME, "tls_active", "TLS Active");
+
+ public static final WidgetPropertyDescriptor propServerIdentity =
+ newStringPropertyDescriptor(WidgetPropertyCategory.RUNTIME, "server_identity", "Server Identity");
+
+ public static final WidgetPropertyDescriptor propClientIdentity =
+ newStringPropertyDescriptor(WidgetPropertyCategory.RUNTIME, "client_identity", "Client Identity");
+
+ public static final WidgetPropertyDescriptor propAuthMethod =
+ newStringPropertyDescriptor(WidgetPropertyCategory.RUNTIME, "auth_method", "Auth Method");
+
+ private volatile WidgetProperty display_mode;
+ private volatile WidgetProperty tls_active;
+ private volatile WidgetProperty server_identity;
+ private volatile WidgetProperty client_identity;
+ private volatile WidgetProperty auth_method;
+
+ public PVASecurityWidget()
+ {
+ super(WIDGET_DESCRIPTOR.getType(), 200, 25);
+ }
+
+ @Override
+ protected void defineProperties(final List> properties)
+ {
+ super.defineProperties(properties);
+ properties.add(display_mode = propDisplayMode.createProperty(this, PVASecurityMode.TLS_STATUS));
+ properties.add(tls_active = propTLSActive.createProperty(this, false));
+ properties.add(server_identity = propServerIdentity.createProperty(this, ""));
+ properties.add(client_identity = propClientIdentity.createProperty(this, ""));
+ properties.add(auth_method = propAuthMethod.createProperty(this, ""));
+ }
+
+ public WidgetProperty propDisplayMode()
+ {
+ return display_mode;
+ }
+
+ public WidgetProperty runtimePropTLSActive()
+ {
+ return tls_active;
+ }
+
+ public WidgetProperty runtimePropServerIdentity()
+ {
+ return server_identity;
+ }
+
+ public WidgetProperty runtimePropClientIdentity()
+ {
+ return client_identity;
+ }
+
+ public WidgetProperty runtimePropAuthMethod()
+ {
+ return auth_method;
+ }
+}
diff --git a/app/display/representation-javafx/.classpath b/app/display/representation-javafx/.classpath
index 3386b2efe1..c6effeb74c 100644
--- a/app/display/representation-javafx/.classpath
+++ b/app/display/representation-javafx/.classpath
@@ -1,11 +1,37 @@
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18,5 +44,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/display/representation-javafx/.project b/app/display/representation-javafx/.project
index dbe279bf03..9b8a5cfbc3 100644
--- a/app/display/representation-javafx/.project
+++ b/app/display/representation-javafx/.project
@@ -10,8 +10,25 @@
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+ org.eclipse.m2e.core.maven2Nature
org.eclipse.jdt.core.javanature
+
+
+ 1775148840228
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BaseWidgetRepresentations.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BaseWidgetRepresentations.java
index b6db502bd7..8576b4491f 100644
--- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BaseWidgetRepresentations.java
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BaseWidgetRepresentations.java
@@ -34,6 +34,7 @@
import org.csstudio.display.builder.model.widgets.PolygonWidget;
import org.csstudio.display.builder.model.widgets.PolylineWidget;
import org.csstudio.display.builder.model.widgets.ProgressBarWidget;
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget;
import org.csstudio.display.builder.model.widgets.RadioWidget;
import org.csstudio.display.builder.model.widgets.RectangleWidget;
import org.csstudio.display.builder.model.widgets.ScaledSliderWidget;
@@ -106,6 +107,7 @@ public Widget createWidget()
entry(PolygonWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new PolygonRepresentation()),
entry(PolylineWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new PolylineRepresentation()),
entry(ProgressBarWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new ProgressBarRepresentation()),
+ entry(PVASecurityWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new PVASecurityRepresentation()),
entry(RadioWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new RadioRepresentation()),
entry(RectangleWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new RectangleRepresentation()),
entry(ScaledSliderWidget.WIDGET_DESCRIPTOR, () -> (WidgetRepresentation) new ScaledSliderRepresentation()),
diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/PVASecurityRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/PVASecurityRepresentation.java
new file mode 100644
index 0000000000..66bd7789d0
--- /dev/null
+++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/PVASecurityRepresentation.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Oak Ridge National Laboratory.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.csstudio.display.builder.representation.javafx.widgets;
+
+import org.csstudio.display.builder.model.DirtyFlag;
+import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
+import org.csstudio.display.builder.model.WidgetProperty;
+import org.csstudio.display.builder.model.WidgetPropertyListener;
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget;
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget.PVASecurityMode;
+import org.epics.vtype.VType;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.SVGPath;
+
+@SuppressWarnings("nls")
+public class PVASecurityRepresentation extends JFXBaseRepresentation
+{
+ private static final String LOCKED_ICON = "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z";
+ private static final String UNLOCKED_ICON = "M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z";
+ private static final String SERVER_ICON = "M20 13H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm13-6H4c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h16c.55 0 1 .45 1 1v6c0 .55-.45 1-1 1zM7 11c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z";
+ private static final String PERSON_ICON = "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z";
+ private static final String SHIELD_ICON = "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z";
+
+ private static final Color TLS_ON = Color.web("#2e7d32");
+ private static final Color TLS_OFF = Color.web("#9e9e9e");
+ private static final Color SERVER = Color.web("#1565c0");
+ private static final Color CLIENT = Color.web("#6a1b9a");
+ private static final Color AUTH = Color.web("#e65100");
+
+ private final DirtyFlag dirty_size = new DirtyFlag();
+ private final DirtyFlag dirty_content = new DirtyFlag();
+ private final WidgetPropertyListener size_listener = this::sizeChanged;
+ private final UntypedWidgetPropertyListener content_listener = this::contentChanged;
+
+ private SVGPath icon;
+ private Label text;
+
+ @Override
+ protected HBox createJFXNode() throws Exception
+ {
+ icon = new SVGPath();
+ icon.setScaleX(0.67);
+ icon.setScaleY(0.67);
+
+ text = new Label();
+
+ final HBox box = new HBox(6, icon, text);
+ box.setAlignment(Pos.CENTER_LEFT);
+ box.setManaged(false);
+ return box;
+ }
+
+ @Override
+ protected void registerListeners()
+ {
+ super.registerListeners();
+ model_widget.propWidth().addPropertyListener(size_listener);
+ model_widget.propHeight().addPropertyListener(size_listener);
+ model_widget.propDisplayMode().addUntypedPropertyListener(content_listener);
+ model_widget.runtimePropTLSActive().addUntypedPropertyListener(content_listener);
+ model_widget.runtimePropServerIdentity().addUntypedPropertyListener(content_listener);
+ model_widget.runtimePropClientIdentity().addUntypedPropertyListener(content_listener);
+ model_widget.runtimePropAuthMethod().addUntypedPropertyListener(content_listener);
+ model_widget.runtimePropValue().addUntypedPropertyListener(content_listener);
+ contentChanged(null, null, null);
+ }
+
+ @Override
+ protected void unregisterListeners()
+ {
+ model_widget.propWidth().removePropertyListener(size_listener);
+ model_widget.propHeight().removePropertyListener(size_listener);
+ model_widget.propDisplayMode().removePropertyListener(content_listener);
+ model_widget.runtimePropTLSActive().removePropertyListener(content_listener);
+ model_widget.runtimePropServerIdentity().removePropertyListener(content_listener);
+ model_widget.runtimePropClientIdentity().removePropertyListener(content_listener);
+ model_widget.runtimePropAuthMethod().removePropertyListener(content_listener);
+ model_widget.runtimePropValue().removePropertyListener(content_listener);
+ super.unregisterListeners();
+ }
+
+ private void sizeChanged(final WidgetProperty property, final Integer old_value, final Integer new_value)
+ {
+ dirty_size.mark();
+ toolkit.scheduleUpdate(this);
+ }
+
+ private void contentChanged(final WidgetProperty> property, final Object old_value, final Object new_value)
+ {
+ dirty_content.mark();
+ toolkit.scheduleUpdate(this);
+ }
+
+ @Override
+ public void updateChanges()
+ {
+ super.updateChanges();
+ if (dirty_size.checkAndClear())
+ jfx_node.resize(model_widget.propWidth().getValue(), model_widget.propHeight().getValue());
+ if (! dirty_content.checkAndClear())
+ return;
+
+ final PVASecurityMode mode = model_widget.propDisplayMode().getValue();
+ switch (mode)
+ {
+ case SERVER_IDENTITY:
+ icon.setContent(SERVER_ICON);
+ icon.setFill(SERVER);
+ text.setText(orDash(model_widget.runtimePropServerIdentity().getValue()));
+ break;
+ case CLIENT_IDENTITY:
+ icon.setContent(PERSON_ICON);
+ icon.setFill(CLIENT);
+ text.setText(orDash(model_widget.runtimePropClientIdentity().getValue()));
+ break;
+ case AUTH_METHOD:
+ icon.setContent(SHIELD_ICON);
+ icon.setFill(AUTH);
+ text.setText(orDash(model_widget.runtimePropAuthMethod().getValue()));
+ break;
+ case TLS_STATUS:
+ default:
+ final VType value = model_widget.runtimePropValue().getValue();
+ if (value == null)
+ {
+ icon.setContent(UNLOCKED_ICON);
+ icon.setFill(TLS_OFF);
+ text.setText("Disconnected");
+ }
+ else if (model_widget.runtimePropTLSActive().getValue())
+ {
+ icon.setContent(LOCKED_ICON);
+ icon.setFill(TLS_ON);
+ text.setText("TLS Secured");
+ }
+ else
+ {
+ icon.setContent(UNLOCKED_ICON);
+ icon.setFill(TLS_OFF);
+ text.setText("Not Secured");
+ }
+ break;
+ }
+ }
+
+ private String orDash(final String text)
+ {
+ if (text == null || text.isBlank())
+ return "—";
+ return text;
+ }
+}
diff --git a/app/display/runtime/.classpath b/app/display/runtime/.classpath
index 82f059f1cd..aff6ac5af5 100644
--- a/app/display/runtime/.classpath
+++ b/app/display/runtime/.classpath
@@ -1,19 +1,35 @@
-
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -24,5 +40,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/display/runtime/.project b/app/display/runtime/.project
index 2f260554b9..52652a9a5d 100644
--- a/app/display/runtime/.project
+++ b/app/display/runtime/.project
@@ -10,8 +10,25 @@
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+ org.eclipse.m2e.core.maven2Nature
org.eclipse.jdt.core.javanature
+
+
+ 1775148824115
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/app/display/runtime/pom.xml b/app/display/runtime/pom.xml
index 3022ea40b7..d141b5d5f1 100644
--- a/app/display/runtime/pom.xml
+++ b/app/display/runtime/pom.xml
@@ -50,6 +50,11 @@
core-pv
5.0.3-SNAPSHOT
+
+ org.phoebus
+ core-pv-pva
+ 5.0.3-SNAPSHOT
+
org.phoebus
app-display-representation-javafx
diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/BaseWidgetRuntimes.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/BaseWidgetRuntimes.java
index 5cd511ad70..e65b2fdbe7 100644
--- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/BaseWidgetRuntimes.java
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/BaseWidgetRuntimes.java
@@ -17,6 +17,7 @@
import org.csstudio.display.builder.model.widgets.EmbeddedDisplayWidget;
import org.csstudio.display.builder.model.widgets.GroupWidget;
import org.csstudio.display.builder.model.widgets.NavigationTabsWidget;
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget;
import org.csstudio.display.builder.model.widgets.ScaledSliderWidget;
import org.csstudio.display.builder.model.widgets.ScrollBarWidget;
import org.csstudio.display.builder.model.widgets.TableWidget;
@@ -44,6 +45,7 @@ public Map>> getWidgetRuntimeFa
entry(GroupWidget.WIDGET_DESCRIPTOR.getType(), () -> new GroupWidgetRuntime()),
entry(ImageWidget.WIDGET_DESCRIPTOR.getType(), () -> new ImageWidgetRuntime()),
entry(NavigationTabsWidget.WIDGET_DESCRIPTOR.getType(), () -> new NavigationTabsRuntime()),
+ entry(PVASecurityWidget.WIDGET_DESCRIPTOR.getType(), () -> new PVASecurityRuntime()),
entry(ScaledSliderWidget.WIDGET_DESCRIPTOR.getType(), () -> new SliderWidgetRuntime()),
entry(ScrollBarWidget.WIDGET_DESCRIPTOR.getType(), () -> new SliderWidgetRuntime()),
entry(StripchartWidget.WIDGET_DESCRIPTOR.getType(), () -> new StripchartWidgetRuntime()),
diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/PVASecurityRuntime.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/PVASecurityRuntime.java
new file mode 100644
index 0000000000..2ab0943a63
--- /dev/null
+++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/internal/PVASecurityRuntime.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Oak Ridge National Laboratory.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.csstudio.display.builder.runtime.internal;
+
+import java.util.Optional;
+import java.util.logging.Level;
+
+import org.csstudio.display.builder.model.widgets.PVASecurityWidget;
+import org.csstudio.display.builder.runtime.WidgetRuntime;
+import org.csstudio.display.builder.runtime.pv.PVFactory;
+import org.csstudio.display.builder.runtime.pv.RuntimePV;
+import org.csstudio.display.builder.runtime.pv.RuntimePVListener;
+import org.epics.vtype.VType;
+import org.phoebus.pv.PV;
+import org.phoebus.pv.PVPool;
+import org.phoebus.pv.pva.PVA_PV;
+
+@SuppressWarnings("nls")
+public class PVASecurityRuntime extends WidgetRuntime
+{
+ private static final String PVA_PREFIX = "pva" + PVPool.SEPARATOR;
+
+ private volatile RuntimePV security_pv;
+ private volatile boolean extra_pv_created = false;
+ private volatile boolean needs_refresh = true;
+
+ private final RuntimePVListener pv_listener = new RuntimePVListener()
+ {
+ @Override
+ public void valueChanged(final RuntimePV pv, final VType value)
+ {
+ if (! needs_refresh)
+ return;
+ needs_refresh = false;
+ updateSecurityProperties(pv);
+ }
+
+ @Override
+ public void disconnected(final RuntimePV pv)
+ {
+ needs_refresh = true;
+ clearSecurityProperties();
+ }
+ };
+
+ @Override
+ public void start()
+ {
+ super.start();
+
+ final String pv_name = widget.propPVName().getValue();
+ if (pv_name == null || pv_name.isBlank())
+ return;
+
+ if (pv_name.startsWith(PVA_PREFIX))
+ {
+ final Optional primary = getPrimaryPV();
+ if (primary.isPresent())
+ {
+ security_pv = primary.get();
+ extra_pv_created = false;
+ }
+ }
+ else
+ {
+ try
+ {
+ security_pv = PVFactory.getPV(PVA_PREFIX + pv_name);
+ extra_pv_created = true;
+ }
+ catch (Exception ex)
+ {
+ logger.log(Level.WARNING, "Cannot create PVA PV for security widget: " + pv_name, ex);
+ }
+ }
+
+ final RuntimePV pv = security_pv;
+ if (pv != null)
+ pv.addListener(pv_listener);
+ }
+
+ @Override
+ public void stop()
+ {
+ final RuntimePV pv = security_pv;
+ if (pv != null)
+ {
+ pv.removeListener(pv_listener);
+ if (extra_pv_created)
+ PVFactory.releasePV(pv);
+ security_pv = null;
+ }
+ extra_pv_created = false;
+ needs_refresh = true;
+ super.stop();
+ }
+
+ private void updateSecurityProperties(final RuntimePV runtime_pv)
+ {
+ final PV pv = runtime_pv.getPV();
+ if (pv instanceof PVA_PV)
+ {
+ final PVA_PV pva_pv = (PVA_PV) pv;
+ final String server_x509 = pva_pv.getServerX509Name();
+ final String client_x509 = pva_pv.getClientX509Name();
+ final String auth_info = pva_pv.getAuthenticationInfo();
+ final String remote_addr = pva_pv.getRemoteAddress();
+
+ widget.runtimePropTLSActive().setValue(pva_pv.isTLS());
+ widget.runtimePropAuthMethod().setValue(safe(auth_info));
+ widget.runtimePropServerIdentity().setValue(
+ server_x509 != null ? server_x509 : safe(remote_addr));
+ widget.runtimePropClientIdentity().setValue(
+ client_x509 != null ? client_x509 : safe(auth_info));
+ }
+ else
+ clearSecurityProperties();
+ }
+
+ private void clearSecurityProperties()
+ {
+ widget.runtimePropTLSActive().setValue(false);
+ widget.runtimePropServerIdentity().setValue("");
+ widget.runtimePropClientIdentity().setValue("");
+ widget.runtimePropAuthMethod().setValue("");
+ }
+
+ private String safe(final String value)
+ {
+ return value == null ? "" : value;
+ }
+}