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">