You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2023/01/28 13:27:47 UTC

[tapestry-5] branch better-page-invalidation updated: TAP5-2744: first pass at page dependency graph

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 <th...@arsmachina.com.br>
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">