You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by jo...@apache.org on 2021/09/10 11:01:19 UTC

[isis] 02/07: ISIS-2846 Referrer introduced for easier link tree re-construction

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

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

commit c7a3900a8a50d42f00209a4c93caf18b3934474b
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Mon Sep 6 15:23:24 2021 +0200

    ISIS-2846 Referrer introduced for easier link tree re-construction
---
 .../kroviz/core/aggregator/ActionDispatcher.kt     | 19 +++---
 .../kroviz/core/aggregator/AggregatorWithLayout.kt | 22 ++++--
 .../kroviz/core/aggregator/BaseAggregator.kt       | 11 ++-
 .../kroviz/core/aggregator/CollectionAggregator.kt | 44 ++++++------
 .../core/aggregator/DomainTypesAggregator.kt       |  4 +-
 .../kroviz/core/aggregator/ObjectAggregator.kt     | 22 +++---
 .../kroviz/core/aggregator/RestfulDispatcher.kt    |  4 +-
 .../isis/client/kroviz/core/event/EventStore.kt    |  4 +-
 .../isis/client/kroviz/core/event/LogEntry.kt      | 16 ++---
 .../client/kroviz/core/event/LogEntryDecorator.kt  | 35 ++++++++++
 .../isis/client/kroviz/core/event/ResourceProxy.kt | 16 +++--
 .../kroviz/core/event/ResourceSpecification.kt     |  3 +-
 .../client/kroviz/ui/diagram/LinkTreeDiagram.kt    | 55 ++++++++++-----
 .../apache/isis/client/kroviz/ui/diagram/Node.kt   | 30 +++++++++
 .../isis/client/kroviz/ui/diagram/PumlCode.kt      | 78 ++++++++++++++++++++++
 15 files changed, 277 insertions(+), 86 deletions(-)

diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ActionDispatcher.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ActionDispatcher.kt
index aaf1ec0..68603d9 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ActionDispatcher.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ActionDispatcher.kt
@@ -20,8 +20,8 @@ package org.apache.isis.client.kroviz.core.aggregator
 
 import org.apache.isis.client.kroviz.core.event.LogEntry
 import org.apache.isis.client.kroviz.to.*
-import org.apache.isis.client.kroviz.ui.dialog.ActionPrompt
 import org.apache.isis.client.kroviz.ui.core.Constants
+import org.apache.isis.client.kroviz.ui.dialog.ActionPrompt
 import org.apache.isis.client.kroviz.utils.Point
 import org.apache.isis.client.kroviz.utils.StringUtils
 
@@ -29,14 +29,15 @@ class ActionDispatcher(private val at: Point = Point(100, 100)) : BaseAggregator
 
     override fun update(logEntry: LogEntry, subType: String) {
         val to = logEntry.getTransferObject()
+        val referrer = logEntry.url
         when {
             to is Action -> {
                 to.links.forEach { link ->
                     if (link.isInvokeAction()) {
                         when (link.method) {
-                            Method.GET.name -> process(to, link)
-                            Method.POST.name -> invoke(to, link)
-                            Method.PUT.name -> process(to, link)
+                            Method.GET.name -> process(to, link, referrer = referrer)
+                            Method.POST.name -> invoke(to, link, referrer = referrer)
+                            Method.PUT.name -> process(to, link, referrer = referrer)
                         }
                     }
                 }
@@ -54,17 +55,17 @@ class ActionDispatcher(private val at: Point = Point(100, 100)) : BaseAggregator
         }
     }
 
-    private fun process(action: Action, link: Link, aggregator: BaseAggregator = this) {
+    private fun process(action: Action, link: Link, aggregator: BaseAggregator = this, referrer: String) {
         when {
             link.hasArguments() -> ActionPrompt(action = action).open(at)
-            link.relation() == Relation.INVOKE -> invoke(action, link)
-            else -> invoke(link, aggregator)
+            link.relation() == Relation.INVOKE -> invoke(action, link, referrer)
+            else -> invoke(link, aggregator, referrer = referrer)
         }
     }
 
-    private fun invoke(action: Action, link: Link) {
+    private fun invoke(action: Action, link: Link, referrer: String) {
         val title = StringUtils.deCamel(action.id)
-        invoke(link, ObjectAggregator(title))
+        invoke(link, ObjectAggregator(title), referrer = referrer)
     }
 
     /**
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/AggregatorWithLayout.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/AggregatorWithLayout.kt
index 46998dc..4bea388 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/AggregatorWithLayout.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/AggregatorWithLayout.kt
@@ -1,5 +1,6 @@
 package org.apache.isis.client.kroviz.core.aggregator
 
+import org.apache.isis.client.kroviz.core.event.LogEntry
 import org.apache.isis.client.kroviz.core.model.DisplayModelWithLayout
 import org.apache.isis.client.kroviz.layout.Layout
 import org.apache.isis.client.kroviz.to.Represention
@@ -7,8 +8,15 @@ import org.apache.isis.client.kroviz.to.TObject
 import org.apache.isis.client.kroviz.ui.core.Constants
 
 abstract class AggregatorWithLayout : BaseAggregator() {
+    // parentUrl is to be set in update
+    // and to be used in subsequent invocations
+    var parentUrl: String? = null
 
-    protected fun handleLayout(layout: Layout, dm: DisplayModelWithLayout) {
+    override fun update(logEntry: LogEntry, subType: String) {
+        parentUrl = logEntry.url
+    }
+
+    protected fun handleLayout(layout: Layout, dm: DisplayModelWithLayout, referrer: String) {
         if (dm.layout == null) {
             dm.addLayout(layout)
             dm.properties.propertyLayoutList.forEach { p ->
@@ -19,25 +27,25 @@ abstract class AggregatorWithLayout : BaseAggregator() {
                     val isDn = l.href.contains("datanucleus")
                     if (!isDn) {
                         //invoking DN links leads to an error
-                        invoke(l, this)
+                        invoke(l, this, referrer = referrer)
                     }
                 }
             }
         }
     }
 
-    protected fun invokeLayoutLink(obj: TObject, aggregator: AggregatorWithLayout) {
+    protected fun invokeLayoutLink(obj: TObject, aggregator: AggregatorWithLayout, referrer :String) {
         val l = obj.getLayoutLink()
         if (l.representation() == Represention.OBJECT_LAYOUT_BS3) {
-            invoke(l, aggregator, Constants.subTypeXml)
+            invoke(l, aggregator, Constants.subTypeXml, referrer)
         } else {
-            invoke(l, aggregator)
+            invoke(l, aggregator, referrer = referrer)
         }
     }
 
-    protected fun invokeIconLink(obj: TObject, aggregator: AggregatorWithLayout) {
+    protected fun invokeIconLink(obj: TObject, aggregator: AggregatorWithLayout, referrer: String) {
         val l = obj.getIconLink()!!
-        invoke(l, aggregator)
+        invoke(l, aggregator, referrer = referrer)
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/BaseAggregator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/BaseAggregator.kt
index 46f4262..edbe93b 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/BaseAggregator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/BaseAggregator.kt
@@ -58,7 +58,8 @@ abstract class BaseAggregator {
         logEntry.setUndefined("no handler found")
         console.log("[BaseAggregator.log] ")
         console.log(logEntry)
-        throw Throwable("no handler found: ${this::class.simpleName}")
+        val className = this::class.simpleName
+        throw Throwable("no handler found: $className")
     }
 
     fun TObject.getLayoutLink(): Link? {
@@ -82,8 +83,12 @@ abstract class BaseAggregator {
         return href.isNotEmpty() && href.endsWith("object-icon")
     }
 
-    protected fun invoke(link: Link, aggregator: BaseAggregator, subType: String = Constants.subTypeJson) {
-        ResourceProxy().fetch(link, aggregator, subType)
+    protected fun invoke(
+            link: Link,
+            aggregator: BaseAggregator,
+            subType: String = Constants.subTypeJson,
+            referrer: String) {
+        ResourceProxy().fetch(link, aggregator, subType, referrer = referrer)
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/CollectionAggregator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/CollectionAggregator.kt
index 99f19b2..fe833c6 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/CollectionAggregator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/CollectionAggregator.kt
@@ -21,6 +21,7 @@ package org.apache.isis.client.kroviz.core.aggregator
 import org.apache.isis.client.kroviz.core.event.EventState
 import org.apache.isis.client.kroviz.core.event.LogEntry
 import org.apache.isis.client.kroviz.core.event.ResourceProxy
+import org.apache.isis.client.kroviz.core.event.ResourceSpecification
 import org.apache.isis.client.kroviz.core.model.CollectionDM
 import org.apache.isis.client.kroviz.layout.Layout
 import org.apache.isis.client.kroviz.to.*
@@ -41,20 +42,21 @@ class CollectionAggregator(actionTitle: String, val parent: ObjectAggregator? =
     }
 
     override fun update(logEntry: LogEntry, subType: String) {
-
+        super.update(logEntry, subType)
         if (logEntry.state == EventState.DUPLICATE) {
             console.log("[CollectionAggregator.update] TODO duplicates should not be propagated to handlers")
             //TODO this may not hold true for changed and deleted objects - object version required to deal with it?
         } else {
+            val referrer = logEntry.url
             when (val obj = logEntry.getTransferObject()) {
                 null -> log(logEntry)
-                is ResultList -> handleList(obj)
-                is TObject -> handleObject(obj)
-                is DomainType -> handleDomainType(obj)
-                is Layout -> handleLayout(obj, dpm as CollectionDM)
+                is ResultList -> handleList(obj, referrer)
+                is TObject -> handleObject(obj, referrer)
+                is DomainType -> handleDomainType(obj, referrer)
+                is Layout -> handleLayout(obj, dpm as CollectionDM, referrer)
                 is Grid -> handleGrid(obj)
-                is Property -> handleProperty(obj)
-                is Collection -> handleCollection(obj)
+                is Property -> handleProperty(obj, referrer)
+                is Collection -> handleCollection(obj, referrer)
                 is Icon -> handleIcon(obj)
                 else -> log(logEntry)
             }
@@ -64,41 +66,41 @@ class CollectionAggregator(actionTitle: String, val parent: ObjectAggregator? =
                     UiManager.openCollectionView(this)
                 }
             } else {
-                val le = LogEntry("")
+                val le = LogEntry(ResourceSpecification(""))
                 parent.update(le, subType)
             }
         }
     }
 
-    private fun handleList(resultList: ResultList) {
+    private fun handleList(resultList: ResultList, referrer: String) {
         if (resultList.resulttype != ResultType.VOID.type) {
             val result = resultList.result!!
             result.value.forEach {
-                invoke(it, this)
+                invoke(it, this, referrer = referrer)
             }
         }
     }
 
-    private fun handleObject(obj: TObject) {
+    private fun handleObject(obj: TObject, referrer: String) {
         dpm.addData(obj)
-        invokeLayoutLink(obj, this)
-        invokeIconLink(obj, this)
+        invokeLayoutLink(obj, this, referrer = referrer)
+        invokeIconLink(obj, this, referrer = referrer)
     }
 
     private fun handleIcon(obj: TransferObject?) {
         (dpm as CollectionDM).addIcon(obj)
     }
 
-    private fun handleDomainType(obj: DomainType) {
+    private fun handleDomainType(obj: DomainType, referrer: String) {
         obj.links.forEach {
             if (it.relation() == Relation.LAYOUT) {
-                invoke(it, this)
+                invoke(it, this, referrer = referrer)
             }
         }
         obj.members.forEach {
             val m = it.value
             if (m.isProperty()) {
-                invoke(m, this)
+                invoke(m, this, referrer = referrer)
             }
         }
     }
@@ -107,7 +109,7 @@ class CollectionAggregator(actionTitle: String, val parent: ObjectAggregator? =
         (dpm as CollectionDM).grid = grid
     }
 
-    private fun handleProperty(p: Property) {
+    private fun handleProperty(p: Property, referrer: String) {
         val dm = dpm as CollectionDM
         if (p.isPropertyDescription()) {
             dm.addPropertyDescription(p)
@@ -115,19 +117,19 @@ class CollectionAggregator(actionTitle: String, val parent: ObjectAggregator? =
             dm.addProperty(p)
             val pdl = p.descriptionLink()
             if (pdl != null) {
-                invoke(pdl, this)
+                invoke(pdl, this, referrer = referrer)
             }
         }
     }
 
-    private fun handleCollection(collection: Collection) {
+    private fun handleCollection(collection: Collection, referrer: String) {
         collection.links.forEach {
             if (it.relation() == Relation.DESCRIBED_BY) {
-                ResourceProxy().fetch(it, this)
+                ResourceProxy().fetch(it, this, referrer = referrer)
             }
         }
         collection.value.forEach {
-            ResourceProxy().fetch(it, this)
+            ResourceProxy().fetch(it, this, referrer = referrer)
         }
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/DomainTypesAggregator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/DomainTypesAggregator.kt
index a3b2fc6..07be0ed 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/DomainTypesAggregator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/DomainTypesAggregator.kt
@@ -63,7 +63,7 @@ class DomainTypesAggregator(val url: String) : BaseAggregator() {
             }
             (dpm as DiagramDM).incNumberOfProperties(propertyList.size)
             propertyList.forEach {
-                invoke(it.value, this)
+                invoke(it.value, this, referrer = "")
             }
         }
     }
@@ -102,7 +102,7 @@ class DomainTypesAggregator(val url: String) : BaseAggregator() {
         }
         (dpm as DiagramDM).numberOfClasses = domainTypeLinkList.size
         domainTypeLinkList.forEach {
-            invoke(it, this)
+            invoke(it, this, referrer = "")
         }
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ObjectAggregator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ObjectAggregator.kt
index c09a0e3..80225fd 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ObjectAggregator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/ObjectAggregator.kt
@@ -43,12 +43,14 @@ class ObjectAggregator(val actionTitle: String) : AggregatorWithLayout() {
     }
 
     override fun update(logEntry: LogEntry, subType: String) {
+        super.update(logEntry, subType)
         if (!logEntry.isUpdatedFromParentedCollection()) {
+            val referrer = logEntry.url
             when (val obj = logEntry.getTransferObject()) {
-                is TObject -> handleObject(obj)
+                is TObject -> handleObject(obj, referrer)
                 is ResultObject -> handleResultObject(obj)
                 is Property -> handleProperty(obj)
-                is Layout -> handleLayout(obj, dpm as ObjectDM)
+                is Layout -> handleLayout(obj, dpm as ObjectDM, referrer)
                 is Grid -> handleGrid(obj)
                 is HttpError -> ErrorDialog(logEntry).open()
                 else -> log(logEntry)
@@ -71,25 +73,25 @@ class ObjectAggregator(val actionTitle: String) : AggregatorWithLayout() {
         }
     }
 
-    fun handleObject(obj: TObject) {
+    fun handleObject(obj: TObject, referrer : String) {
         // After ~/action/invoke is called, the actual object instance (containing properties) needs to be invoked as well.
         // Note that rel.self/href is identical and both are of type TObject. logEntry.url is different, though.
         if (obj.getProperties().size == 0) {
-            invokeInstance(obj)
+            invokeInstance(obj, referrer)
         } else {
             dpm.addData(obj)
         }
         if (collectionMap.isEmpty()) {
-            handleCollections(obj)
+            handleCollections(obj, referrer)
         }
-        invokeLayoutLink(obj, this)
+        invokeLayoutLink(obj, this, referrer = referrer)
     }
 
-    private fun invokeInstance(obj: TObject) {
+    private fun invokeInstance(obj: TObject, referrer: String) {
         val selfLink = obj.links.find { l ->
             l.relation() == Relation.SELF
         }
-        invoke(selfLink!!, this)
+        invoke(selfLink!!, this, referrer = referrer)
     }
 
     fun handleResultObject(resultObject: ResultObject) {
@@ -100,13 +102,13 @@ class ObjectAggregator(val actionTitle: String) : AggregatorWithLayout() {
         return dpm.getObject()
     }
 
-    private fun handleCollections(obj: TObject) {
+    private fun handleCollections(obj: TObject, referrer: String) {
         obj.getCollections().forEach {
             val key = it.id
             val aggregator = CollectionAggregator(key, this)
             collectionMap.put(key, aggregator)
             val link = it.links.first()
-            ResourceProxy().fetch(link, aggregator)
+            ResourceProxy().fetch(link, aggregator, referrer = referrer)
         }
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/RestfulDispatcher.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/RestfulDispatcher.kt
index 1412c7f..13214f0 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/RestfulDispatcher.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/aggregator/RestfulDispatcher.kt
@@ -40,11 +40,11 @@ class RestfulDispatcher() : BaseAggregator() {
     }
 
     private fun invokeNavigation(it: Link) {
-        invoke(it, NavigationDispatcher())
+        invoke(it, NavigationDispatcher(), referrer = "")
     }
 
     private fun invokeSystem(it: Link) {
-        invoke(it, SystemAggregator())
+        invoke(it, SystemAggregator(), referrer = "")
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
index b8fa60a..80f212f 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
@@ -52,7 +52,7 @@ object EventStore {
               method: String,
               body: String = "",
               aggregator: BaseAggregator? = null): LogEntry {
-        val entry = LogEntry(reSpec.url, method, request = body, subType = reSpec.subType)
+        val entry = LogEntry(reSpec, method, request = body)
         if (aggregator != null) {
             entry.addAggregator(aggregator)
         }
@@ -62,7 +62,7 @@ object EventStore {
     }
 
     fun add(reSpec: ResourceSpecification) {
-        val entry = LogEntry(url = reSpec.url)
+        val entry = LogEntry(reSpec)
         log(entry)
         updateStatus(entry)
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
index a7db164..b53d774 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
@@ -52,11 +52,13 @@ enum class EventState(val id: String, val iconName: String, val style: ButtonSty
 
 @Serializable
 data class LogEntry(
-        val url: String,
+        @Contextual val rs: ResourceSpecification,
         val method: String? = "",
         val request: String = "",
-        val subType: String = Constants.subTypeJson,
         @Contextual val createdAt: Date = Date()) {
+    val url:String = rs.url
+    val referrer = rs.referrerUrl
+    val subType = rs.subType
     var state = EventState.INITIAL
     var title: String = ""
     var requestLength: Int = 0 // must be accessible (public) for LogEntryTable
@@ -99,7 +101,7 @@ data class LogEntry(
 
     // alternative constructor for UI events (eg. from user interaction)
     @JsName("secondaryConstructor")
-    constructor(title: String, aggregator: BaseAggregator) : this("", "", "") {
+    constructor(title: String, aggregator: BaseAggregator) : this(ResourceSpecification(""), "", "") {
         this.title = title
         this.addAggregator(aggregator)
         state = EventState.VIEW
@@ -178,7 +180,6 @@ data class LogEntry(
         }
     }
 
-
 // region response
     /**
      * This is for access from the views only.
@@ -250,12 +251,9 @@ data class LogEntry(
         return null
     }
 
-    fun upLink(): Link? {
-        getLinks().forEach { if (it.relation() == Relation.UP) return it }
-        return null
-    }
-
     fun getLinks(): List<Link> {
+//        console.log("[LE.getLinks]")
+//        console.log(obj)
         return if (obj is HasLinks) {
             (obj as HasLinks).getLinks()
         } else {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
index 68e9626..f891eeb 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryDecorator.kt
@@ -73,6 +73,39 @@ class LogEntryDecorator(val logEntry: LogEntry) {
         return children
     }
 
+    fun findChildrenOfLogEntry(): List<LogEntry> {
+        console.log("[LED.findChildrenOfLogEntry]")
+        val children = mutableListOf<LogEntry>()
+        val links = logEntry.getLinks()
+        console.log("[links] ->")
+        console.log(links)
+        console.log(links.size)
+        links.forEach {
+            console.log(it.toString())
+            when (it.relation()) {
+                Relation.UP -> {
+                }
+                Relation.SELF -> {
+                }
+                else -> {
+                    val url = it.href
+                    console.log(url)
+                    val rsj = ResourceSpecification(url, Constants.subTypeJson)
+                    var le = EventStore.findBy(rsj)
+                    if (le == null) {
+                        val rsx = ResourceSpecification(url, Constants.subTypeXml)
+                        le = EventStore.findBy(rsx)
+                    }
+                    console.log(le.toString())
+                    if (le != null) children.add(le)
+                }
+            }
+        }
+        console.log("[children] ->")
+        console.log(children)
+        return children
+    }
+
     private fun findChildrenByReference(): Set<LogEntry> {
         val str = logEntry.response
         val children = mutableSetOf<LogEntry>()
@@ -86,6 +119,7 @@ class LogEntryDecorator(val logEntry: LogEntry) {
 
     fun findChildrenIn(aggregatedList: List<LogEntry>): List<LogEntry> {
         console.log("[LED.findChildrenIn]")
+        console.log(aggregatedList)
         val selfUrl = href
         val children = mutableListOf<LogEntry>()
         aggregatedList.forEach {
@@ -93,6 +127,7 @@ class LogEntryDecorator(val logEntry: LogEntry) {
                 children.add(it)
             }
         }
+        console.log(children)
         return children
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceProxy.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceProxy.kt
index 733a1de..92d60a1 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceProxy.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceProxy.kt
@@ -26,6 +26,7 @@ import org.apache.isis.client.kroviz.handler.ResponseHandler
 import org.apache.isis.client.kroviz.to.Link
 import org.apache.isis.client.kroviz.to.TObject
 import org.apache.isis.client.kroviz.ui.core.Constants
+import org.apache.isis.client.kroviz.ui.diagram.Node
 
 /**
  * Facade for RoXmlHttpRequest. If a resource is being fetched, it:
@@ -39,7 +40,7 @@ class ResourceProxy {
 
     // there may be more than one aggt - which may break this code
     // we are coming from a parented collection ...
-    // we can assume the object hat been loaded as part of the collection before
+    // we can assume the object has been loaded before as part of the collection
     fun load(tObject: TObject) {
         val aggregator = ObjectAggregator(tObject.title)
         // ASSUMPTION: there can be max one LogEntry for an Object
@@ -65,15 +66,22 @@ class ResourceProxy {
     fun fetch(link: Link,
               aggregator: BaseAggregator? = null,
               subType: String = Constants.subTypeJson,
-              isRest: Boolean = true) {
-        val rs = ResourceSpecification(link.href)
+              isRest: Boolean = true,
+              referrer: String = "") {
+        val rs = ResourceSpecification(link.href, referrerUrl = referrer)
         val isCached = when (val le = EventStore.findBy(rs)) {
             null -> false
             else -> le.isCached(rs, link.method)
         }
         when {
             isCached -> processCached(rs, aggregator)
-            !isCached && isRest -> RoXmlHttpRequest(aggregator).process(link, subType)
+            !isCached && isRest -> {
+                if (aggregator is AggregatorWithLayout) {
+                    val child = Node(link.href)
+//FIXME                    aggregator.root.add(child)
+                }
+                RoXmlHttpRequest(aggregator).process(link, subType)
+            }
             !isCached && !isRest -> RoXmlHttpRequest(aggregator).processNonREST(link, subType)
         }
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceSpecification.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceSpecification.kt
index 90e8395..ee18faf 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceSpecification.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ResourceSpecification.kt
@@ -23,7 +23,8 @@ import org.apache.isis.client.kroviz.utils.StringUtils
 
 class ResourceSpecification(
         val url: String,
-        val subType: String = Constants.subTypeJson) {
+        val subType: String = Constants.subTypeJson,
+        val referrerUrl: String = "") {
 
     fun isRedundant(): Boolean {
         return when {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
index c6f6533..9e6c480 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
@@ -1,3 +1,21 @@
+/*
+ *  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.client.kroviz.ui.diagram
 
 import org.apache.isis.client.kroviz.core.aggregator.BaseAggregator
@@ -21,13 +39,12 @@ object LinkTreeDiagram {
         val entryList: List<LogEntry> = EventStore.findAllBy(aggregator)
         val root = findRoot(entryList)
         if (root == null) {
-            code += "* /$NL"
+            code += "* / $NL"
             code += createNodes(entryList, 2)
         } else {
             code += root.asPumlNode(1)
             val led = LogEntryDecorator(root)
-            val childList = led.findChildrenIn(entryList)
-            console.log("[LTD.build]")
+            val childList = led.findChildrenOfLogEntry()
             console.log(aggregator)
             console.log(entryList)
             code += createChildNodes(childList, 2)
@@ -41,8 +58,8 @@ object LinkTreeDiagram {
         childList.forEach {
             code += createNode(it, level)
             val led = LogEntryDecorator(it)
-            val kidSet = led.findChildrenIn(childList)
-            code += createChildNodes(kidSet, level +1)
+            val kidSet = led.findChildrenOfLogEntry()
+            code += createChildNodes(kidSet, level + 1)
         }
         return code
     }
@@ -82,26 +99,32 @@ object LinkTreeDiagram {
 
     fun LogEntry.asPumlNode(level: Int): String {
         val led = LogEntryDecorator(this)
-        val title = StringUtils.shortTitle(this.url, protocolHostPort)
+        val url = this.url
+        val title = StringUtils.shortTitle(url, protocolHostPort)
         val type = led.selfType()
         val depth = "*".repeat(level)
-        var answer = "$depth:..//<<$type>>//..$NL**$title**$NL"
-        answer += "----$NL"
-        answer += traceInfo(this)
-        return answer + ";" + NL
+        val pc = PumlCode()
+        pc.add(depth).add(":")
+        pc.addStereotype(type)
+        pc.addLink(url, title)
+        pc.addHorizontalLine()
+        pc.add(traceInfo(this))
+        pc.addLine(";")
+        return pc.code
     }
 
-    private fun traceInfo(logEntry: LogEntry) : String {
+    private fun traceInfo(logEntry: LogEntry): String {
         val obj = logEntry.obj!!
-        var answer = "__" + obj::class.simpleName + "__" + NL
+        val className = obj::class.simpleName!!
+        val pc = PumlCode().addClass(className)
         if (obj is HasLinks) {
             obj.links.forEach {
-                answer += StringUtils.shortTitle(it.href, protocolHostPort) + NL
+                val url = it.href
+                val title = StringUtils.shortTitle(url, protocolHostPort)
+                pc.addLink(url, title)
             }
         }
-        console.log("[LTD.traceInfo]")
-        console.log(answer)
-        return answer
+        return pc.code
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/Node.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/Node.kt
new file mode 100644
index 0000000..19bc68c
--- /dev/null
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/Node.kt
@@ -0,0 +1,30 @@
+/*
+ *  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.client.kroviz.ui.diagram
+
+class Node(val key: String) {
+
+    var parent: Node? = null
+    val children = mutableListOf<Node>()
+
+    fun add(child: Node) {
+        children.add(child)
+    }
+
+}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/PumlCode.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/PumlCode.kt
new file mode 100644
index 0000000..ac5ea8d
--- /dev/null
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/PumlCode.kt
@@ -0,0 +1,78 @@
+/*
+ *  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.client.kroviz.ui.diagram
+
+class PumlCode() {
+
+    private val NL = "\n"
+
+    var code = ""
+
+    fun add(s: String): PumlCode {
+        code += s
+        return this
+    }
+
+    fun addLine(s: String): PumlCode {
+        code += s + NL
+        return this
+    }
+
+    fun addStereotype(s: String): PumlCode {
+        var result = "<<" + s + ">>"
+        result = italic(result)
+        result = center(result)
+        code += result + NL
+        return this
+    }
+
+    fun addLink(url: String, title: String): PumlCode {
+        var result = "[[" + url + " "+ title + "]]"
+        result = bold(result)
+        code += result + NL
+        return this
+    }
+
+    fun addClass(s: String): PumlCode {
+        val result = underline("(C) "+ s)
+        code += result + NL
+        return this
+    }
+
+    private fun center(s: String): String {
+        return ".." + s + ".."
+    }
+
+    private fun italic(s: String): String {
+        return "//" + s + "//"
+    }
+
+    private fun bold(s: String): String {
+        return "**" + s + "**"
+    }
+
+    private fun underline(s: String): String {
+        return "__" + s + "__"
+    }
+
+    fun addHorizontalLine(): PumlCode {
+        code += "----" + NL
+        return this
+    }
+}