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

thiagohp pushed a commit to branch better-page-invalidation
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/better-page-invalidation by 
this push:
     new 8dae1cce0 TAP5-2744: first pass at page dependency graph
8dae1cce0 is described below

commit 8dae1cce00de43286f0ac972100acceca24592bd
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Sat Jan 28 10:27:29 2023 -0300

    TAP5-2744: first pass at page dependency graph
---
 .../tapestry5/corelib/pages/PageCatalog.java       |  42 +++++-
 .../ComponentDependencyGraphvizGenerator.java      |  30 +++++
 .../ComponentDependencyGraphvizGeneratorImpl.java  | 149 +++++++++++++++++++++
 .../services/ComponentInstantiatorSourceImpl.java  |  12 ++
 .../META-INF/assets/tapestry5/PageCatalog.js       |   9 ++
 .../apache/tapestry5/corelib/pages/PageCatalog.tml |   6 +
 6 files changed, 244 insertions(+), 4 deletions(-)

diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
index 0a5925e86..0728b6fe2 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
@@ -21,9 +21,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.alerts.AlertManager;
 import org.apache.tapestry5.annotations.InjectComponent;
+import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.annotations.Persist;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
@@ -44,6 +46,7 @@ import org.apache.tapestry5.func.Reducer;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
 import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.internal.PageCatalogTotals;
+import 
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
 import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
 import org.apache.tapestry5.internal.services.PageSource;
 import org.apache.tapestry5.internal.services.ReloadHelper;
@@ -56,6 +59,8 @@ import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
+import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
 /**
@@ -135,6 +140,22 @@ public class PageCatalog
     @ComponentClasses 
     private InvalidationEventHub classesInvalidationEventHub;
     
+    @Inject
+    private JavaScriptSupport javaScriptSupport;
+    
+    @Inject
+    private ComponentDependencyGraphvizGenerator 
componentDependencyGraphvizGenerator;
+
+    @Inject
+    private ComponentClassResolver componentClassResolver;
+
+    @Inject
+    private AjaxResponseRenderer ajaxResponseRenderer;
+    
+    @Inject
+    @Path("classpath:/META-INF/assets/tapestry5/PageCatalog.js")
+    private Asset pageCatalogJs;
+
     void pageLoaded()
     {
         model = beanModelSource.createDisplayModel(Page.class, messages);
@@ -347,10 +368,18 @@ public class PageCatalog
         return dependencies;
     }
     
-    public Object onPageStructure(String name)
+    public void onPageStructure(String pageName)
     {
-        selectedPage = pageSource.getPage(name);
-        return request.isXHR() ? pageStructureZone.getBody() : null;
+        selectedPage = pageSource.getPage(pageName);
+        ajaxResponseRenderer.addRender("pageStructureZone", 
pageStructureZone.getBody());
+        ajaxResponseRenderer.addCallback((JavaScriptSupport js) -> {
+            js.importJavaScriptLibrary(pageCatalogJs);
+            
js.importJavaScriptLibrary("https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.umd.js";);
+            final String graphvizSource = 
getGraphvizSource(getSelectedPageClassName());
+            System.out.println(graphvizSource);
+            js.addScript("showGraphviz('%s', '%s');", pageName, 
+                    graphvizSource.replace("\n", " "));
+        });
     }
     
     public String getDisplayLogicalName() 
@@ -360,7 +389,6 @@ public class PageCatalog
 
     public String getPageClassName() 
     {
-        
         return getClassName(page);
     }
 
@@ -433,4 +461,10 @@ public class PageCatalog
     {
         return resolver.getLogicalName(className);
     }
+    
+    private String getGraphvizSource(String className)
+    {
+        return componentDependencyGraphvizGenerator.generate(className);
+    }
+    
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGenerator.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGenerator.java
new file mode 100644
index 000000000..d0a83396f
--- /dev/null
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGenerator.java
@@ -0,0 +1,30 @@
+// Copyright 2023 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.internal.services;
+
+/**
+ * Service that generates a <a 
href="https://graphviz.org/doc/info/lang.html";>Graphviz DOT description file</a>
+ * for a given component's dependency graph or for the whole set of 
dependencies of all components.
+ * @since 5.8.3
+ */
+public interface ComponentDependencyGraphvizGenerator {
+
+    /**
+     * Generates the Graphviz DOT file and returns it as a Strting.
+     * @param classNames the component (including page) class names to 
generate the dependency graph.
+     * @return
+     */
+    String generate(String ... classNames);
+    
+}
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
new file mode 100644
index 000000000..b8fb2bc19
--- /dev/null
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyGraphvizGeneratorImpl.java
@@ -0,0 +1,149 @@
+// Copyright 2023 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.internal.services;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tapestry5.services.ComponentClassResolver;
+
+public class ComponentDependencyGraphvizGeneratorImpl implements 
ComponentDependencyGraphvizGenerator {
+    
+    final private ComponentClassResolver componentClassResolver;
+    
+    final private ComponentDependencyRegistry componentDependencyRegistry;
+
+
+    public 
ComponentDependencyGraphvizGeneratorImpl(ComponentDependencyRegistry 
componentDependencyRegistry, 
+            ComponentClassResolver componentClassResolver) 
+    {
+        super();
+        this.componentDependencyRegistry = componentDependencyRegistry;
+        this.componentClassResolver = componentClassResolver;
+    }
+
+    @Override
+    public String generate(String... classNames) 
+    {
+        
+        final StringBuilder dotFile = new StringBuilder("digraph {\n\n");
+        
+        dotFile.append("\tfontname=\"Helvetica,Arial,sans-serif\";\n");
+        dotFile.append("\tnode [fontname=\"Helvetica,Arial,sans-serif\"];\n");
+        dotFile.append("\tedge [fontname=\"Helvetica,Arial,sans-serif\"];\n");
+        dotFile.append("\tnode [shape=box];\n\n");
+        
+        final Set<String> allClasses = new HashSet<>();
+        
+        for (String className : classNames)
+        {
+            addDependencies(className, allClasses);
+        }
+        
+        final StringBuilder dependencySection = new StringBuilder();
+        
+        for (String className : allClasses) 
+        {
+            final Node node = 
createNode(componentClassResolver.getLogicalName(className), className);
+            dotFile.append(getNodeDefinition(node));
+            for (String dependency : node.dependencies)
+            {
+                dependencySection.append(getNodeDependencyDefinition(node, 
dependency));
+            }
+        }
+        
+        dotFile.append("\n");
+        dotFile.append(dependencySection);
+        dotFile.append("\n");
+
+        dotFile.append("}");
+        
+        return dotFile.toString();
+    }
+    
+    private String getNodeDefinition(Node node) 
+    {
+        return String.format("\t%s [label=\"%s\", tooltip=\"%s\"];\n", 
node.id, node.label, node.className);
+    }
+    
+    private String getNodeDependencyDefinition(Node node, String dependency) 
+    {
+        return String.format("\t%s -> %s\n", node.id, 
escapeNodeId(getNodeLabel(dependency)));
+    }
+
+    private String getNodeLabel(String className) 
+    {
+        final String logicalName = 
componentClassResolver.getLogicalName(className);
+        return getNodeLabel(className, logicalName, false);
+    }
+
+    private static String getNodeLabel(String className, final String 
logicalName, boolean beautify) {
+        return logicalName != null ? beautifyLogicalName(logicalName) : 
(beautify ? beautifyClassName(className) : className);
+    }
+    
+    private static String beautifyLogicalName(String logicalName) {
+        return logicalName.startsWith("core/") ? logicalName.replace("core/", 
"") : logicalName;
+    }
+
+    private static String beautifyClassName(String className)
+    {
+        String name = className.substring(className.lastIndexOf('.') + 1);
+        if (className.contains(".base."))
+        {
+            name += " (base class)";
+        }
+        else if (className.contains(".mixins."))
+        {
+            name += " (mixin)";
+        }
+        return name;
+    }
+
+    private static String escapeNodeId(String label) {
+        return label.replace('.', '_').replace('/', '_');
+    }
+
+    private void addDependencies(String className, Set<String> allClasses) 
+    {
+        allClasses.add(className);
+        for (String dependency : 
componentDependencyRegistry.getDependencies(className))
+        {
+            addDependencies(dependency, allClasses);
+        }
+    }
+
+    private Node createNode(String logicalName, String className) 
+    {
+        return new Node(logicalName, className, 
componentDependencyRegistry.getDependencies(className));
+    }
+
+    private static final class Node {
+
+        final private String id;
+        final private String className;
+        final private String label;
+        final private Set<String> dependencies = new HashSet<>();
+        
+        public Node(String logicalName, String className, Collection<String> 
dependencies) 
+        {
+            super();
+            this.label = getNodeLabel(className, logicalName, true);
+            this.id = escapeNodeId(getNodeLabel(className, logicalName, 
false));
+            this.className = className;
+            this.dependencies.addAll(dependencies);
+        }
+
+    }
+}
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
index 8d112cb13..cff65325e 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
@@ -188,6 +188,9 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
                     .map(ClassNameHolder::getClassName)
                     .collect(Collectors.toList()));
             
+            // TODO Remove this when multiple classloaders are figured out
+            initializeService();
+            
         }
     }
 
@@ -225,6 +228,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
         classToInstantiator.clear();
         proxyFactory.clearCache();
 //        classToModel.clear();
+        initializeService();
     }
 
     /**
@@ -234,6 +238,14 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
      */
     private void initializeService()
     {
+        if (manager == null)
+        {
+            logger.info("Initializing page pool");
+        }
+        else 
+        {
+            logger.info("Restarting page pool");
+        }
         PlasticManagerBuilder builder = 
PlasticManager.withClassLoader(parent).delegate(this)
                 .packages(controlledPackageNames);
 
diff --git 
a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/PageCatalog.js 
b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/PageCatalog.js
new file mode 100644
index 000000000..c3be055a5
--- /dev/null
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/PageCatalog.js
@@ -0,0 +1,9 @@
+console.log("PageCatalog.js");
+function showGraphviz(pageName, dot) {
+       var hpccWasm = 
require("https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.umd.js";);
+    hpccWasm.Graphviz.load().then(graphviz => {                        
+               const svg = graphviz.dot(dot);
+               const div = document.getElementById(pageName + "-graphviz");
+               div.innerHTML = graphviz.layout(dot, "svg", "dot");
+       });
+};
\ No newline at end of file
diff --git 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
index cc024bf0a..688f8bd05 100644
--- 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
+++ 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
@@ -65,6 +65,12 @@
                        </ul>
                </div>
            </div>
+           <div class="panel panel-default vert-offset" t:type="If" 
t:test="selectedPage">
+               <div class="panel-heading">${selectedPage.name}'s dependency 
tree</div>
+<!--           <pre 
id="${selectedPage.name}-graphviz-source">${getGraphVizSource(selectedPageClassName)}</pre>
 -->
+               <div class="panel-body" id="${selectedPage.name}-graphviz">
+               </div>
+           </div>
        </t:zone>          
 
     <div class="panel panel-default vert-offset">

Reply via email to