You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/03/16 19:36:23 UTC

[isis] branch edge updated: ISIS-2573: xray: reduce number of tree nodes rendered

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch edge
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/edge by this push:
     new 22dbab3  ISIS-2573: xray: reduce number of tree nodes rendered
22dbab3 is described below

commit 22dbab3b6bf174eb4416543bf917289d56d7fe8f
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Mar 16 20:36:05 2021 +0100

    ISIS-2573: xray: reduce number of tree nodes rendered
    
    prepare for member executor integration with xray
---
 .../commons/internal/debug/xray/XrayDataModel.java |  11 ++-
 .../commons/internal/debug/xray/XrayModel.java     |  16 ++--
 .../isis/commons/internal/debug/xray/XrayUi.java   |  38 +++++++++
 .../debug/xray/sequence/SequenceDiagram.java       |   5 +-
 commons/src/main/resources/xray/key-value.png      | Bin 0 -> 4545 bytes
 commons/src/main/resources/xray/sequence.png       | Bin 0 -> 4195 bytes
 .../org/apache/isis/core/runtime/events/_Xray.java |  25 +++---
 .../apache/isis/core/runtime/util/XrayUtil.java    |  54 +++++++++++++
 .../executor/MemberExecutorServiceDefault.java     |  17 ++--
 .../isis/core/runtimeservices/executor/_Xray.java  |  83 +++++++++++++++++++
 .../isis/core/runtimeservices/session/_Xray.java   |  20 +++--
 .../applayer/ApplicationLayerTestFactory.java      |  90 +++++++--------------
 12 files changed, 261 insertions(+), 98 deletions(-)

diff --git a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayDataModel.java b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayDataModel.java
index 0229255..d95a96d 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayDataModel.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayDataModel.java
@@ -43,6 +43,7 @@ public abstract class XrayDataModel extends HasIdAndLabel {
     public abstract void render(JScrollPane detailPanel);
     public abstract String getId();
     public abstract String getLabel();
+    public abstract String getIconResource();
 
     // -- PREDEFINED DATA MODELS
     
@@ -53,9 +54,13 @@ public abstract class XrayDataModel extends HasIdAndLabel {
         
         @EqualsAndHashCode.Exclude
         private final Map<String, String> data = new TreeMap<>();
+        
         private final String id;
         private final String label;
         
+        @EqualsAndHashCode.Exclude
+        private final String iconResource = "/xray/key-value.png";
+        
         @Override
         public void render(JScrollPane panel) {
             String[] columnNames = {"Key", "Value"};
@@ -88,6 +93,9 @@ public abstract class XrayDataModel extends HasIdAndLabel {
         private final String id;
         private final String label;
         
+        @EqualsAndHashCode.Exclude
+        private final String iconResource = "/xray/sequence.png";
+        
         private final static Color COLOR_SILVER = new Color(0xf5, 0xf5, 0xf5);
         private final static Color BACKGROUND_COLOR = COLOR_SILVER;
         private final static Color BORDER_COLOR = Color.GRAY;
@@ -124,9 +132,6 @@ public abstract class XrayDataModel extends HasIdAndLabel {
             panel.setViewportView(canvas);
             
         }
-
-
-
        
         
     }
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayModel.java b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayModel.java
index 1ffd851..9cc0f7d 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayModel.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayModel.java
@@ -22,9 +22,12 @@ import java.util.Optional;
 import java.util.Stack;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.MutableTreeNode;
 
+import org.apache.isis.commons.internal.base._Strings;
+
 public interface XrayModel {
     
     MutableTreeNode getRootNode();
@@ -43,10 +46,12 @@ public interface XrayModel {
     
     // -- DATA LOOKUP
     
-    default Optional<XrayDataModel.Sequence> lookupSequence(String sequenceId) {
-        return lookupNode(sequenceId)
-        .map(node->
-             (XrayDataModel.Sequence)((DefaultMutableTreeNode) node).getUserObject());
+    default Optional<XrayDataModel.Sequence> lookupSequence(final @Nullable String sequenceId) {
+        return _Strings.isNullOrEmpty(sequenceId)
+                ? Optional.empty()
+                : lookupNode(sequenceId)
+                    .map(node->
+                        (XrayDataModel.Sequence)((DefaultMutableTreeNode) node).getUserObject());
     }
     
     // -- STACKS
@@ -65,9 +70,6 @@ public interface XrayModel {
         }
         
     }
-
-    
-
     
     
 }
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayUi.java b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayUi.java
index d0ae307..da522ba 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayUi.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/XrayUi.java
@@ -18,6 +18,7 @@
  */
 package org.apache.isis.commons.internal.debug.xray;
 
+import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -27,10 +28,12 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
+import java.net.URL;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
+import javax.swing.ImageIcon;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
@@ -42,11 +45,14 @@ import javax.swing.JTree;
 import javax.swing.SwingUtilities;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
 import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeCellRenderer;
 
 import org.apache.isis.commons.collections.Can;
 
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 
 public class XrayUi extends JFrame {
@@ -144,6 +150,8 @@ public class XrayUi extends JFrame {
             }
         });
         
+        tree.setCellRenderer(new XrayTreeCellRenderer((DefaultTreeCellRenderer) tree.getCellRenderer()));
+        
         tree.addMouseListener(new MouseListener() {
             
             @Override public void mouseReleased(MouseEvent e) {}
@@ -230,7 +238,37 @@ public class XrayUi extends JFrame {
         return detailScrollPane;
     }
 
+    // -- CUSTOM TREE NODE ICONS
     
+    @RequiredArgsConstructor
+    class XrayTreeCellRenderer implements TreeCellRenderer {
+        
+        final DefaultTreeCellRenderer delegate; 
+
+        public Component getTreeCellRendererComponent(
+                JTree tree,
+                Object value, 
+                boolean selected, 
+                boolean expanded, 
+                boolean leaf, 
+                int row, 
+                boolean hasFocus) {
+            
+            val label = (DefaultTreeCellRenderer)
+                    delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+            
+            Object o = ((DefaultMutableTreeNode) value).getUserObject();
+            if (o instanceof XrayDataModel) {
+                XrayDataModel dataModel = (XrayDataModel) o;
+                URL imageUrl = getClass().getResource(dataModel.getIconResource());
+                if (imageUrl != null) {
+                    label.setIcon(new ImageIcon(imageUrl));
+                }
+                label.setText(dataModel.getLabel());
+            }
+            return label;
+        }
+    }
     
 
     
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/sequence/SequenceDiagram.java b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/sequence/SequenceDiagram.java
index b65c5f9..2801e0f 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/sequence/SequenceDiagram.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/debug/xray/sequence/SequenceDiagram.java
@@ -34,6 +34,7 @@ import org.apache.isis.commons.internal.base._Refs.IntReference;
 import org.apache.isis.commons.internal.debug.xray.sequence._Graphics.TextBlock;
 
 import lombok.Getter;
+import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
@@ -51,13 +52,13 @@ public class SequenceDiagram {
         return this;
     }
 
-    public void enter(String from, String to, String label) {
+    public void enter(final @NonNull String from, final @NonNull String to, String label) {
         val p0 = participantsById.computeIfAbsent(from, id->new Participant(aliases.getOrDefault(id, id)));
         val p1 = participantsById.computeIfAbsent(to, id->new Participant(aliases.getOrDefault(id, id)));
         connections.add(new Connection(p0, p1, label, false));
     }
 
-    public void exit(String from, String to, String label) {
+    public void exit(final @NonNull String from, final @NonNull String to, String label) {
         val p1 = participantsById.computeIfAbsent(to, id->new Participant(aliases.getOrDefault(id, id)));
         val p0 = participantsById.computeIfAbsent(from, id->new Participant(aliases.getOrDefault(id, id)));
         connections.add(new Connection(p0, p1, label, true));
diff --git a/commons/src/main/resources/xray/key-value.png b/commons/src/main/resources/xray/key-value.png
new file mode 100644
index 0000000..4f7a95d
Binary files /dev/null and b/commons/src/main/resources/xray/key-value.png differ
diff --git a/commons/src/main/resources/xray/sequence.png b/commons/src/main/resources/xray/sequence.png
new file mode 100644
index 0000000..d0a3d34
Binary files /dev/null and b/commons/src/main/resources/xray/sequence.png differ
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
index be7035c..8735007 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
@@ -23,6 +23,7 @@ import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.commons.internal.debug.xray.XrayDataModel;
 import org.apache.isis.commons.internal.debug.xray.XrayUi;
 import org.apache.isis.core.interaction.session.InteractionTracker;
+import org.apache.isis.core.runtime.util.XrayUtil;
 
 import lombok.val;
 
@@ -57,16 +58,16 @@ final class _Xray {
         
         val threadId = _Probe.currentThreadId();
         
-        val sequenceId = iaTracker.getConversationId()
-        .map(interactionId->String.format("seq-%s", interactionId))
+        val sequenceId = XrayUtil.currentSequenceId(iaTracker)
         .orElse(null);
         
         XrayUi.updateModel(model->{
     
+            val seq = model.lookupSequence(sequenceId);
+            
             // if no sequence diagram available, that we can append to,
             // then at least add a node to the left tree
-            if(sequenceId==null
-                    || !model.lookupSequence(sequenceId).isPresent()) {
+            if(!seq.isPresent()) {
                 val uiThreadNode = model.getThreadNode(threadId);
                 model.addContainerNode(
                         uiThreadNode,
@@ -74,8 +75,7 @@ final class _Xray {
                 return;
             }
             
-            model.lookupSequence(sequenceId)
-            .ifPresent(sequence->{
+            seq.ifPresent(sequence->{
                 val sequenceData = sequence.getData();
                 sequenceData.enter("thread", "tx", "before completion");
             });
@@ -93,16 +93,16 @@ final class _Xray {
         
         val threadId = _Probe.currentThreadId();
         
-        val sequenceId = iaTracker.getConversationId()
-        .map(interactionId->String.format("seq-%s", interactionId))
-        .orElse(null);
+        val sequenceId = XrayUtil.currentSequenceId(iaTracker)
+                .orElse(null);
         
         XrayUi.updateModel(model->{
     
+            val seq = model.lookupSequence(sequenceId);
+            
             // if no sequence diagram available, that we can append to,
             // then at least add a node to the left tree
-            if(sequenceId==null
-                    || !model.lookupSequence(sequenceId).isPresent()) {
+            if(!seq.isPresent()) {
                 val uiThreadNode = model.getThreadNode(threadId);
                 model.addContainerNode(
                         uiThreadNode,
@@ -110,8 +110,7 @@ final class _Xray {
                 return;
             }
             
-            model.lookupSequence(sequenceId)
-            .ifPresent(sequence->{
+            seq.ifPresent(sequence->{
                 val sequenceData = sequence.getData();
                 sequenceData.exit("tx", "thread", txInfo);
             });
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/util/XrayUtil.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/util/XrayUtil.java
new file mode 100644
index 0000000..24655ef
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/util/XrayUtil.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.isis.core.runtime.util;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apache.isis.core.interaction.session.InteractionTracker;
+
+import lombok.NonNull;
+import lombok.val;
+
+public final class XrayUtil {
+
+    /**
+     * Returns the sequence diagram data model's id, that is bound to the current thread and interaction.
+     * @param iaTracker
+     */
+    public static Optional<String> currentSequenceId(final @NonNull InteractionTracker iaTracker) {
+        return iaTracker.getConversationId()
+                .map(XrayUtil::sequenceId);
+    }
+    
+    public static String sequenceId(final @NonNull UUID uuid) {
+        return String.format("seq-%s", uuid);
+    }
+
+    public static String currentThreadId() {
+        val ct = Thread.currentThread();
+        return String.format("thread-%d-%s", ct.getId(), ct.getName());
+    }
+
+    public static String currentThreadLabel() {
+        val ct = Thread.currentThread();
+        return String.format("Thread-%d [%s])", ct.getId(), ct.getName());
+    }
+    
+}
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
index 2f73cef..cfa7381 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
@@ -83,7 +83,7 @@ import lombok.extern.log4j.Log4j2;
 public class MemberExecutorServiceDefault
 implements MemberExecutorService {
 
-    private final @Getter InteractionTracker isisInteractionTracker;
+    private final @Getter InteractionTracker interactionTracker;
     private final @Getter IsisConfiguration configuration;
     private final @Getter ObjectManager objectManager;
     private final @Getter ClockService clockService;
@@ -96,7 +96,7 @@ implements MemberExecutorService {
 
     @Override
     public Optional<InternalInteraction> getInteraction() {
-        return isisInteractionTracker.currentInteraction()
+        return interactionTracker.currentInteraction()
                 .map(InternalInteraction.class::cast);
     }
 
@@ -119,6 +119,8 @@ implements MemberExecutorService {
         
         CommandPublishingFacet.ifPublishingEnabledForCommand(
                 command, owningAction, facetHolder, ()->command.updater().setPublishingEnabled(true));
+        
+        val xrayHandle = _Xray.enterInvocation(interaction);
 
         val actionId = owningAction.getIdentifier();
         log.debug("about to invoke action {}", actionId);
@@ -176,8 +178,9 @@ implements MemberExecutorService {
             executionPublisher.get().publishActionInvocation(priorExecution);
         }
 
-        return filteredIfRequired(method, returnedAdapter, interactionInitiatedBy);
-
+        val result = filteredIfRequired(method, returnedAdapter, interactionInitiatedBy);
+        _Xray.exitInvocation(xrayHandle);
+        return result;
     }
     @Override
     public ManagedObject setOrClearProperty(
@@ -199,6 +202,8 @@ implements MemberExecutorService {
         CommandPublishingFacet.ifPublishingEnabledForCommand(
                 command, owningProperty, facetHolder, ()->command.updater().setPublishingEnabled(true));
 
+        val xrayHandle = _Xray.enterInvocation(interaction);
+        
         val propertyId = owningProperty.getIdentifier();
 
         val targetManagedObject = head.getTarget();
@@ -234,7 +239,9 @@ implements MemberExecutorService {
             executionPublisher.get().publishPropertyEdit(priorExecution);
         }
 
-        return getObjectManager().adapt(targetPojo);
+        val result = getObjectManager().adapt(targetPojo);
+        _Xray.exitInvocation(xrayHandle);
+        return result;
 
     }
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/_Xray.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/_Xray.java
new file mode 100644
index 0000000..0c3815d
--- /dev/null
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/_Xray.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.isis.core.runtimeservices.executor;
+
+import javax.annotation.Nullable;
+
+import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.commons.internal.debug.xray.XrayUi;
+import org.apache.isis.core.runtime.util.XrayUtil;
+
+import lombok.Builder;
+import lombok.NonNull;
+import lombok.val;
+
+final class _Xray {
+
+    static Handle enterInvocation(Interaction interaction) {
+        if(!XrayUi.isXrayEnabled()
+                || interaction.getCurrentExecution()==null) {
+            return null;
+        }
+        
+        val mId = interaction.getCurrentExecution().getMemberIdentifier();
+        
+        val command = interaction.getCommand();
+        
+        val handle = Handle.builder()
+                .sequenceId(XrayUtil.sequenceId(interaction.getInteractionId()))
+                .caller("thread")
+                .callee(mId.getLogicalIdentityString("#"))
+                .build();
+        
+        val enteringLabel = String.format("invoking");
+
+        XrayUi.updateModel(model->{
+            model.lookupSequence(handle.sequenceId)
+            .ifPresent(sequence->{
+                val sequenceData = sequence.getData();
+                sequenceData.enter(handle.caller, handle.callee, enteringLabel);
+            });
+        });
+        
+        return handle;
+    }
+
+    static void exitInvocation(final @Nullable Handle handle) {
+        if(handle==null) {
+            return; // x-ray is not enabled
+        }
+        
+        XrayUi.updateModel(model->{
+            model.lookupSequence(handle.sequenceId)
+            .ifPresent(sequence->{
+                val sequenceData = sequence.getData();
+                sequenceData.exit(handle.callee, handle.caller);
+            });
+        });
+    }
+    
+    @Builder
+    static final class Handle {
+        final @NonNull String sequenceId;
+        final @NonNull String caller;
+        final @NonNull String callee;
+    }
+
+}
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/_Xray.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/_Xray.java
index e42e664..8b9f3ea 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/_Xray.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/_Xray.java
@@ -23,6 +23,7 @@ import java.util.Stack;
 import org.apache.isis.commons.internal.debug.xray.XrayDataModel;
 import org.apache.isis.commons.internal.debug.xray.XrayUi;
 import org.apache.isis.core.interaction.session.AuthenticationLayer;
+import org.apache.isis.core.runtime.util.XrayUtil;
 
 import lombok.val;
 
@@ -40,28 +41,31 @@ final class _Xray {
         val interactionId = afterEnter.peek().getInteractionSession().getInteractionId();
         val executionContext = afterEnter.peek().getExecutionContext();
         
+        val threadId = XrayUtil.currentThreadId();
+        
         val ct = Thread.currentThread();
         val threadLabel = String.format("Thread-%d\n%s", ct.getId(), ct.getName());
         
         XrayUi.updateModel(model->{
             
-            val sequenceId = String.format("seq-%s", interactionId);
-            val iaLabel = String.format("Interaction\n%s", interactionId);
+            val sequenceId = XrayUtil.sequenceId(interactionId);
+            val iaLabel = String.format("Interaction-%s", interactionId);
+            val iaLabelMultiline = String.format("Interaction\n%s", interactionId);
             val iaOpeningLabel = String.format("open interaction\n%s", 
                     executionContext.getUser().toString().replace(", ", ",\n"));
             
             if(authStackSize==1) {
-                val uiThreadNode = model.getThreadNode(threadLabel);
+                val uiThreadNode = model.getThreadNode(threadId);
                 
-                val uiTopAuthLayerNode = model.addContainerNode(uiThreadNode, iaLabel);
+                //val uiTopAuthLayerNode = model.addContainerNode(uiThreadNode, iaLabel);
                 
                 val sequenceData = model.addDataNode(
-                            uiTopAuthLayerNode, 
-                            new XrayDataModel.Sequence(sequenceId, "Sequence Diagam"))
+                            uiThreadNode,//uiTopAuthLayerNode, 
+                            new XrayDataModel.Sequence(sequenceId, iaLabel))
                         .getData();
                 
                 sequenceData.alias("thread", threadLabel);
-                sequenceData.alias("ia-0", iaLabel);
+                sequenceData.alias("ia-0", iaLabelMultiline);
                 
                 sequenceData.enter("thread", "ia-0", iaOpeningLabel);
                 
@@ -87,7 +91,7 @@ final class _Xray {
         
         final int authStackSize = beforeClose.size();
         val interactionId = beforeClose.peek().getInteractionSession().getInteractionId();
-        val sequenceId = String.format("seq-%s", interactionId);
+        val sequenceId = XrayUtil.sequenceId(interactionId);
         
         XrayUi.updateModel(model->{
             
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java
index 31ad824..1635340 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java
@@ -62,6 +62,7 @@ import org.apache.isis.core.interaction.session.InteractionTracker;
 import org.apache.isis.core.metamodel.interactions.managed.PropertyInteraction;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.runtime.util.XrayUtil;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTrackerDefault;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
 import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
@@ -126,20 +127,35 @@ public class ApplicationLayerTestFactory {
             final Consumer<VerificationStage> verifier) {
 
         return _Lists.of(
-                programmaticTest("Programmatic Execution", 
-                        given, verifier, this::programmaticExecution),
+                
+                dynamicTest("close interaction session stack (if any)", interactionFactory::closeSessionStack),
+                
+                interactionTest("Programmatic Execution", 
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION_WHEN_PROGRAMMATIC, 
+                        this::programmaticExecution),
                 interactionTest("Interaction Api Execution", 
-                        given, verifier, this::interactionApiExecution),
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION, 
+                        this::interactionApiExecution),
                 interactionTest("Wrapper Sync Execution w/o Rules", 
-                        given, verifier, this::wrapperSyncExecutionNoRules),
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION, 
+                        this::wrapperSyncExecutionNoRules),
                 interactionTest("Wrapper Sync Execution w/ Rules (expected to fail w/ DisabledException)", 
-                        given, verifier, this::wrapperSyncExecutionWithFailure),
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION, 
+                        this::wrapperSyncExecutionWithFailure),
                 interactionTest("Wrapper Async Execution w/o Rules", 
-                        given, verifier, this::wrapperAsyncExecutionNoRules),
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION, 
+                        this::wrapperAsyncExecutionNoRules),
                 interactionTest("Wrapper Async Execution w/ Rules (expected to fail w/ DisabledException)", 
-                        given, verifier, this::wrapperAsyncExecutionWithFailure),
+                        given, verifier, 
+                        VerificationStage.POST_INTERACTION, 
+                        this::wrapperAsyncExecutionWithFailure),
                 
-                dynamicTest("wait for xray viewer", XrayUi::waitForShutdown)
+                dynamicTest("wait for xray viewer (if any)", XrayUi::waitForShutdown)
                 
                 );
     }
@@ -151,33 +167,11 @@ public class ApplicationLayerTestFactory {
         boolean run(Runnable given, Consumer<VerificationStage> verifier) throws Exception;
     }
     
-    private DynamicTest programmaticTest(
-            final String displayName,
-            final Runnable given,
-            final Consumer<VerificationStage> verifier,
-            final InteractionTestRunner interactionTestRunner) {
-        
-        return dynamicTest(displayName, ()->{
-            
-            xrayAddTest(displayName);
-            
-            Assert.assertTrue(interactionFactory.isInInteractionSession());
-            
-            val isSuccessfulRun = interactionTestRunner.run(given, verifier);
-                    
-            interactionFactory.closeSessionStack();
-            
-            if(isSuccessfulRun) {
-                verifier.accept(VerificationStage.POST_INTERACTION_WHEN_PROGRAMMATIC);
-            }
-
-        });
-    }
-    
     private DynamicTest interactionTest(
             final String displayName,
             final Runnable given,
             final Consumer<VerificationStage> verifier,
+            final VerificationStage onSuccess,
             final InteractionTestRunner interactionTestRunner) {
         
         return dynamicTest(displayName, ()->{
@@ -198,7 +192,7 @@ public class ApplicationLayerTestFactory {
             interactionFactory.closeSessionStack();
             
             if(isSuccesfulRun) {
-                verifier.accept(VerificationStage.POST_INTERACTION);
+                verifier.accept(onSuccess);
             }
                         
         });
@@ -458,7 +452,8 @@ public class ApplicationLayerTestFactory {
             val book = repository.allInstances(JdoBook.class).listIterator().next();
             transactionalBookConsumer.accept(book);
 
-            //FIXME trigger publishing of entity changes (flush queue)
+            //FIXME ... should not be required explicitly here
+            // trigger publishing of entity changes (flush queue)
             entityChangeTrackerProvider.get().onPreCommit(null);
         })
         .optionalElseFail();
@@ -468,54 +463,29 @@ public class ApplicationLayerTestFactory {
     
     // -- XRAY
     
-    //private final Stack<MutableTreeNode> nodeStack = new Stack<>();
-
     private void xrayAddTest(String name) {
         
-        val threadId = _Probe.currentThreadId();
+        val threadId = XrayUtil.currentThreadId();
         
         XrayUi.updateModel(model->{
-            val newNode = model.addContainerNode(
+            model.addContainerNode(
                     model.getThreadNode(threadId), 
                     String.format("Test: %s", name));
                 
         });  
         
-//        XrayUi.updateModel(model->{
-//            val newNode = model.addContainerNode(
-//                    model.getRootNode(), 
-//                    String.format("Test: %s", name));
-//            nodeStack.clear();
-//            nodeStack.push(newNode);    
-//        });
     }
     
     private void xrayEnterTansaction(Propagation propagation) {
-//        XrayUi.updateModel(model->{
-//            val newNode = model.addContainerNode(
-//                    nodeStack.peek(),
-//                    String.format("Transactional %s", propagation.name()));
-//            nodeStack.push(newNode);
-//        });
     }
     
     private void xrayExitTansaction() {
-        //nodeStack.pop();
     }
     
     private void xrayEnterInteraction(Optional<Interaction> currentInteraction) {
-//        XrayUi.updateModel(model->{
-//            val newNode = model.addContainerNode(
-//                    nodeStack.peek(), 
-//                    currentInteraction.isPresent()
-//                        ? String.format("Interaction %s", currentInteraction.get().getInteractionId())
-//                        : "Iteraction: none");
-//            nodeStack.push(newNode);
-//        });
     }
     
     private void xrayExitInteraction() {
-        //nodeStack.pop();
     }
 
 }