This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch javax
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/javax by this push:
new fb40bbe65 TAP5-2779: multiple classloader fixes
fb40bbe65 is described below
commit fb40bbe6585b36b643b92ecff57b3cabbd67b66d
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Fri May 17 09:42:21 2024 -0300
TAP5-2779: multiple classloader fixes
---
.../internal/plastic/PlasticClassLoader.java | 70 +++-
.../internal/plastic/PlasticClassPool.java | 20 +-
.../corelib/pages/ComponentLibraries.java | 163 +++++++++-
.../tapestry5/corelib/pages/ExceptionReport.java | 6 +
.../tapestry5/corelib/pages/PageCatalog.java | 1 +
.../corelib/pages/PageClassLoaderContexts.java | 104 ++++++
.../tapestry5/internal/ThrowawayClassLoader.java | 105 ++++++
.../ComponentDependencyGraphvizGeneratorImpl.java | 28 +-
.../services/ComponentDependencyRegistry.java | 13 +
.../services/ComponentDependencyRegistryImpl.java | 124 ++++++-
.../services/ComponentInstantiatorSourceImpl.java | 64 +++-
.../services/ComponentModelSourceImpl.java | 52 ++-
.../internal/services/PageSourceImpl.java | 89 ++---
.../apache/tapestry5/modules/DashboardModule.java | 10 +-
.../apache/tapestry5/modules/PageLoadModule.java | 34 +-
.../services/pageload/PageClassLoaderContext.java | 52 ++-
.../pageload/PageClassLoaderContextManager.java | 6 +
.../PageClassLoaderContextManagerImpl.java | 358 ++++++++++++++-------
.../tapestry5/corelib/pages/ComponentLibraries.tml | 289 ++++++++++-------
.../apache/tapestry5/corelib/pages/PageCatalog.tml | 2 +-
.../corelib/pages/PageClassLoaderContexts.tml | 17 +
tapestry-core/src/test/app1/AtComponentType.tml | 6 +
tapestry-core/src/test/app1/GridDemo.tml | 2 +-
.../integration/app1/pages/AtComponentType.java | 24 ++
.../tapestry5/integration/app1/pages/GridDemo.java | 6 +
.../ComponentDependencyRegistryImplTest.java | 149 +++++++--
tapestry-core/src/test/resources/log4j.properties | 1 +
27 files changed, 1413 insertions(+), 382 deletions(-)
diff --git
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
index 85a263b75..591ba3729 100644
---
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
+++
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2011, 2023, 2024 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.
@@ -14,6 +14,14 @@
package org.apache.tapestry5.internal.plastic;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -35,6 +43,10 @@ public class PlasticClassLoader extends ClassLoader
private String tag;
+ private Map<String, Class<?>> cache;
+
+ private static List<String> log = new ArrayList<>();
+
public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate
delegate)
{
super(parent);
@@ -50,24 +62,36 @@ public class PlasticClassLoader extends ClassLoader
if (loadedClass != null)
return loadedClass;
-
+
if (shouldInterceptClassLoading(name))
{
- Class<?> c = null;
- if ((filter != null && filter.test(name)) || (filter == null
&& delegate.shouldInterceptClassLoading(name)))
- {
- c = delegate.loadAndTransformClass(name);
- }
- else if (alternativeClassloading != null)
+
+ Class<?> c = getFromCache(name);
+
+ if (c == null)
{
- c = alternativeClassloading.apply(name);
+
+ if ((filter != null && filter.test(name)) || (filter ==
null && delegate.shouldInterceptClassLoading(name)))
+ {
+ c = delegate.loadAndTransformClass(name);
+ }
+ else if (alternativeClassloading != null)
+ {
+ c = alternativeClassloading.apply(name);
+ }
+
+ if (cache != null && c != null)
+ {
+ cache.put(name, c);
+ }
+
}
if (c == null)
{
return super.loadClass(name, resolve);
}
-
+
if (resolve)
resolveClass(c);
@@ -110,6 +134,10 @@ public class PlasticClassLoader extends ClassLoader
public void setTag(String tag)
{
this.tag = tag;
+ if (cache == null)
+ {
+ cache = Collections.synchronizedMap(new HashMap<>());
+ }
}
/**
@@ -126,9 +154,27 @@ public class PlasticClassLoader extends ClassLoader
@Override
public String toString()
{
- final String superToString = super.toString();
- final String id =
superToString.substring(superToString.indexOf('@')).trim();
+ final String id = getClassLoaderId();
return String.format("PlasticClassLoader[%s, tag=%s, parent=%s]", id,
tag, getParent());
}
+ public String getClassLoaderId() {
+ final String superToString = super.toString();
+ return superToString.substring(superToString.indexOf('@')).trim();
+ }
+
+ private Class<?> getFromCache(String name)
+ {
+ Class<?> c = null;
+ if (cache != null && cache.containsKey(name))
+ {
+ c = cache.get(name);
+ }
+ if (c == null && getParent() instanceof PlasticClassLoader)
+ {
+ c = ((PlasticClassLoader) getParent()).getFromCache(name);
+ }
+ return c;
+ }
+
}
diff --git
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
index 9daa47d51..0bc3bcb7c 100644
---
a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
+++
b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
@@ -753,7 +753,18 @@ public class PlasticClassPool implements
ClassLoaderDelegate, Opcodes, PlasticCl
private FieldInstrumentations getFieldInstrumentations(String
classInternalName)
{
- FieldInstrumentations result = instrumentations.get(classInternalName);
+
+ // Check whether parent pool already has instrumentations for
+ // that class to avoid a duplicated class definition attempt
+ // when running tapestry-core in multiple classloader mode.
+ PlasticClassPool current = this;
+ FieldInstrumentations result;
+ do
+ {
+ result = current.instrumentations.get(classInternalName);
+ current = current.parent;
+ }
+ while (current != null && result == null);
if (result != null)
{
@@ -845,6 +856,13 @@ public class PlasticClassPool implements
ClassLoaderDelegate, Opcodes, PlasticCl
throw new RuntimeException(e);
}
}
+
+ @Override
+ public String toString()
+ {
+ return "PlasticClassPool [loader=" + loader + "]";
+ }
+
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
index 4ebaad2f1..83e4cfa27 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ComponentLibraries.java
@@ -21,11 +21,20 @@ import java.util.List;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.annotations.Cached;
+import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.OnEvent;
+import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
import org.apache.tapestry5.annotations.WhitelistAccessOnly;
+import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
+import org.apache.tapestry5.internal.plastic.ClassLoaderDelegate;
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
+import
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
+import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
+import
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
import org.apache.tapestry5.ioc.annotations.Description;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
@@ -59,6 +68,9 @@ public class ComponentLibraries
};
private static enum Type { PAGE, COMPONENT, MIXIN }
+
+ @InjectComponent
+ private Zone zone;
@Inject
private ComponentClassResolver componentClassResolver;
@@ -97,6 +109,39 @@ public class ComponentLibraries
@Inject
private ComponentLibraryInfoSource componentLibraryInfoSource;
+ @Inject
+ private ComponentDependencyRegistry componentDependencyRegistry;
+
+ @Inject
+ private ComponentDependencyGraphvizGenerator
componentDependencyGraphvizGenerator;
+
+ @Property
+ private String selectedComponent;
+
+ @Property
+ private String dependency;
+
+ @Persist
+ @Property
+ private boolean showEverything;
+
+// void onActivate(List<String> context)
+// {
+// if (context.size() > 0)
+// {
+// selectedComponent = String.join("/", context);
+// }
+// else
+// {
+// selectedComponent = null;
+// }
+// }
+//
+// Object[] onPassivate()
+// {
+// return selectedComponent.split("/");
+// }
+
@Cached
public List<LibraryMapping> getLibraryMappings()
{
@@ -105,7 +150,7 @@ public class ComponentLibraries
// add all the library mappings, except the "" (empty string) one.
for (LibraryMapping libraryMapping :
componentClassResolver.getLibraryMappings())
{
- if (!"".equals(libraryMapping.libraryName)) {
+ if (showEverything || !"".equals(libraryMapping.libraryName)) {
mappings.add(libraryMapping);
}
}
@@ -132,6 +177,7 @@ public class ComponentLibraries
private List<String> filter(final List<String> allNames)
{
List<String> logicalNames = new ArrayList<String>();
+ final List<LibraryMapping> libraryMappings = getLibraryMappings();
for (String name : allNames)
{
@@ -140,6 +186,25 @@ public class ComponentLibraries
{
logicalNames.add(name);
}
+ else
+ {
+ if (libraryMapping.libraryName.equals(""))
+ {
+ boolean isWebappLibrary = true;
+ for (LibraryMapping otherLibraryMapping : libraryMappings)
{
+ if (!libraryMapping.equals(otherLibraryMapping) &&
+
name.startsWith(otherLibraryMapping.libraryName + "/"))
+ {
+ isWebappLibrary = false;
+ break;
+ }
+ }
+ if (isWebappLibrary)
+ {
+ logicalNames.add(name);
+ }
+ }
+ }
}
return logicalNames;
@@ -211,7 +276,12 @@ public class ComponentLibraries
@Cached(watch = "logicalName")
public Description getDescription() throws ClassNotFoundException
{
- return Class.forName(getClassName()).getAnnotation(Description.class);
+ try {
+ return
Class.forName(getClassName()).getAnnotation(Description.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
}
public boolean isClassHasTags() throws ClassNotFoundException
@@ -325,5 +395,94 @@ public class ComponentLibraries
object.put(propertyName, value);
}
}
+
+ public String getGraphvizValue()
+ {
+ return componentDependencyGraphvizGenerator.generate(
+ getClassName(selectedComponent));
+ }
+
+ public String getClassName(String logicalName)
+ {
+ return componentClassResolver.getClassName(logicalName);
+ }
+
+ public String getComponentClassName()
+ {
+ return getClassName(selectedComponent);
+ }
+
+ public List<String> getDependencies()
+ {
+ final String className =
componentClassResolver.getClassName(selectedComponent);
+ final List<String> dependencies = new ArrayList<>();
+
dependencies.addAll(componentDependencyRegistry.getDependencies(className,
DependencyType.INJECT_PAGE));
+
dependencies.addAll(componentDependencyRegistry.getDependencies(className,
DependencyType.SUPERCLASS));
+
dependencies.addAll(componentDependencyRegistry.getDependencies(className,
DependencyType.USAGE));
+ Collections.sort(dependencies);
+ return dependencies;
+ }
+
+ public List<String> getDependents()
+ {
+ final String className =
componentClassResolver.getClassName(selectedComponent);
+ List<String> dependents = new ArrayList<>(
+ componentDependencyRegistry.getDependents(className));
+ Collections.sort(dependents);
+ return dependents;
+ }
+
+ public String getDisplayLogicalName()
+ {
+ return componentClassResolver.getLogicalName(dependency);
+ }
+
+ public Object onSelectComponent(String selectedComponent)
+ {
+ this.selectedComponent = selectedComponent;
+ final String className =
componentClassResolver.getClassName(selectedComponent);
+ if (!componentDependencyRegistry.contains(className))
+ {
+
+ final ClassLoader classLoader = new
ThrowawayClassLoader(getClass().getClassLoader());
+
+ try
+ {
+
componentDependencyRegistry.register(classLoader.loadClass(className));
+ } catch (ClassNotFoundException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ return zone.getBody();
+ }
+
+ public Object getContext()
+ {
+ return logicalName;
+ }
+
+ public Object onReset()
+ {
+ selectedComponent = null;
+ return zone.getBody();
+ }
+
+ public Object onShowEverything()
+ {
+ showEverything = true;
+ return zone.getBody();
+ }
+
+ public Object onShowRestricted()
+ {
+ showEverything = false;
+ return zone.getBody();
+ }
+
+ public String getLibraryName()
+ {
+ return !libraryMapping.libraryName.isEmpty() ?
libraryMapping.libraryName : "Webapp's own component library";
+ }
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index f11d80548..985b38fa6 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -41,6 +41,7 @@ import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.ExceptionReporter;
import org.apache.tapestry5.services.PageRenderLinkSource;
import org.apache.tapestry5.services.URLEncoder;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
import java.net.MalformedURLException;
import java.net.URL;
@@ -123,6 +124,10 @@ public class ExceptionReport extends AbstractInternalPage
implements ExceptionRe
@Inject
@ComponentClasses
private InvalidationEventHub classesInvalidationHub;
+
+ @Inject
+ @Property
+ private PageClassLoaderContextManager pageClassLoaderContextManager;
private String failurePage;
@@ -187,6 +192,7 @@ public class ExceptionReport extends AbstractInternalPage
implements ExceptionRe
public void reportException(Throwable exception)
{
+
System.out.print(pageClassLoaderContextManager.getRoot().toRecursiveString());
rootException = exception;
rootURL = baseURLSource.getBaseURL(request.isSecure());
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 974a4a9e4..ba28b0a98 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
@@ -227,6 +227,7 @@ public class PageCatalog
void onActionFromPreloadPageClassLoaderContexts()
{
pageClassLoaderContextManager.preload();
+ alertManager.warn("Component dependency information and page
classloader contexts preloaded.");
}
Object onClearPage(String className)
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
new file mode 100644
index 000000000..54a12b256
--- /dev/null
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.java
@@ -0,0 +1,104 @@
+// Copyright 2024 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.corelib.pages;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
+import org.apache.tapestry5.annotations.WhitelistAccessOnly;
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContext;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
+
+/**
+ * Shows information about the page classloader contexts.
+ */
+@UnknownActivationContextCheck(false)
+@WhitelistAccessOnly
+public class PageClassLoaderContexts
+{
+
+ @Inject
+ private PageClassLoaderContextManager pageClassLoaderContextManager;
+
+ void onRender(MarkupWriter writer)
+ {
+ final PageClassLoaderContext root =
pageClassLoaderContextManager.getRoot();
+ render(root, writer);
+ }
+
+ private void render(PageClassLoaderContext context, MarkupWriter writer)
+ {
+
+ final int classes = context.getClassNames().size();
+ writer.element("li");
+ writer.element("details");
+ writer.element("summary");
+ writer.element("span", "class", "glyphicon glyphicon-zoom-in");
+ writer.end(); // span
+ writer.write(context.getName());
+ writer.write(" (");
+ writer.write(((PlasticClassLoader)
context.getClassLoader()).getClassLoaderId());
+ writer.write(", ");
+ writer.write(String.valueOf(classes));
+ if (classes > 1)
+ {
+ writer.write(" classes)");
+ }
+ else
+ {
+ writer.write(" class)");
+ }
+ writer.end(); // summary
+
+ if (!context.isRoot() && !context.getClassNames().isEmpty())
+ {
+ final List<String> classNames = new
ArrayList<>(context.getClassNames());
+ Collections.sort(classNames);
+ writer.element("ul");
+ for (String className : classNames)
+ {
+ writer.element("li").text(className);
+ writer.end(); // li
+ }
+ writer.end(); // ul
+ }
+
+ writer.end(); // details
+
+ final List<PageClassLoaderContext> children = new
ArrayList<>(context.getChildren());
+
+ if (!children.isEmpty())
+ {
+
children.sort(Comparator.comparing(PageClassLoaderContext::getName));
+ writer.element("ul");
+ for (PageClassLoaderContext child : children)
+ {
+ writer.element("li");
+ render(child, writer);
+ writer.end(); // li
+ }
+ writer.end(); // ul
+ }
+
+ writer.end(); //li
+ }
+
+}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
new file mode 100644
index 000000000..62c133210
--- /dev/null
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ThrowawayClassLoader.java
@@ -0,0 +1,105 @@
+// Copyright 2024 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;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
+
+public class ThrowawayClassLoader extends ClassLoader
+{
+
+ final private ClassLoader parent;
+
+ public ThrowawayClassLoader(ClassLoader parent)
+ {
+ super(parent);
+ this.parent = parent;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException
+ {
+ synchronized (getClassLoadingLock(name))
+
+ {
+ // First, check if the class has already been loaded
+ Class<?> c = findLoadedClass(name);
+ if (c == null) {
+ if (name.contains(".base.") || name.contains(".pages.") ||
+ name.contains(".components.") ||
name.contains(".mixins."))
+ {
+ final byte[] bytes =
PlasticInternalUtils.readBytecodeForClass(parent, name, true);
+ c = defineClass(name, bytes, 0, bytes.length);
+ if (resolve)
+ {
+ resolveClass(c);
+ }
+ }
+ else
+ {
+ c = parent.loadClass(name);
+ }
+ }
+ return c;
+ }
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+
+ final String className =
"org.apache.tapestry5.corelib.components.BeanEditor";
+
+ final ClassLoader parentClassLoader =
ThrowawayClassLoader.class.getClassLoader();
+ ClassLoader classLoader1 = create(parentClassLoader);
+ ClassLoader classLoader2 = create(parentClassLoader);
+
+ System.out.println("Parent class loader 1: " + parentClassLoader);
+ System.out.println("Class loader 1 : " + classLoader1);
+ System.out.println("Class loader 2 : " + classLoader2);
+
+ Class class1 = classLoader1.loadClass(className);
+ Class class2 = classLoader2.loadClass(className);
+ Class class3 = parentClassLoader.loadClass(className);
+
+ System.out.println("Class 1 : " + class1.getClassLoader());
+ System.out.println("Class 2 : " + class2.getClassLoader());
+ System.out.println("Class 3 : " + class3.getClassLoader());
+
+ assertNotEquals(class1, class2);
+ assertNotEquals(class1, class3);
+ assertNotEquals(class2, class3);
+
+ }
+
+ public static Class<?> load(final String className)
+ {
+ ThrowawayClassLoader loader = new ThrowawayClassLoader(
+ ThrowawayClassLoader.class.getClassLoader());
+ try
+ {
+ return loader.loadClass(className);
+ } catch (ClassNotFoundException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static ClassLoader create(final ClassLoader parentClassLoader) {
+// return
TapestryInternalUtils.createThrowawayClassloader(parentClassLoader);
+ return new ThrowawayClassLoader(parentClassLoader);
+ }
+
+}
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
index 24671c003..ef9cf1017 100644
---
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
@@ -59,12 +59,7 @@ public class ComponentDependencyGraphvizGeneratorImpl
implements ComponentDepend
for (String className : classNames)
{
createNode(className, nodeMap);
- for (DependencyType dependencyType : DependencyType.values())
- {
- addDependencies(className, allClasses, dependencyType,
nodeMap);
- }
-
-
+ addDependencies(className, allClasses, nodeMap);
}
final List<Node> nodes = new ArrayList<>(nodeMap.values());
@@ -141,16 +136,19 @@ public class ComponentDependencyGraphvizGeneratorImpl
implements ComponentDepend
return label.replace('.', '_').replace('/', '_');
}
- private void addDependencies(String className, Set<String> allClasses,
DependencyType type, Map<String, Node> nodeMap)
+ private void addDependencies(String className, Set<String> allClasses,
Map<String, Node> nodeMap)
{
if (!allClasses.contains(className))
{
createNode(className, nodeMap);
- for (String dependency :
componentDependencyRegistry.getDependencies(className, type))
+ allClasses.add(className);
+ for (DependencyType type : DependencyType.values())
{
- addDependencies(dependency, allClasses, type, nodeMap);
+ for (String dependency :
componentDependencyRegistry.getDependencies(className, type))
+ {
+ addDependencies(dependency, allClasses, nodeMap);
+ }
}
- allClasses.add(className);
}
}
@@ -188,7 +186,8 @@ public class ComponentDependencyGraphvizGeneratorImpl
implements ComponentDepend
}
}
- private static final class Node {
+ private static final class Node
+ {
final private String id;
final private String className;
@@ -204,5 +203,12 @@ public class ComponentDependencyGraphvizGeneratorImpl
implements ComponentDepend
this.dependencies.addAll(dependencies);
}
+ @Override
+ public String toString()
+ {
+ return "Node [id=" + id + ", className=" + className + ",
dependencies=" + dependencies + ", label=" + label + "]";
+ }
+
}
+
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
index ad79630f4..314c8e513 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
@@ -67,6 +67,13 @@ public interface ComponentDependencyRegistry {
*/
void register(Class<?> clasz);
+ /**
+ * Register all the dependencies of a given class and uses a given
+ * classloader to load other classes if needed.
+ * @since 5.8.7
+ */
+ void register(Class<?> clasz, ClassLoader classLoader);
+
/**
* Register all the dependencies of a given component.
*/
@@ -105,6 +112,12 @@ public interface ComponentDependencyRegistry {
*/
Set<String> getDependencies(String className, DependencyType type);
+ /**
+ * Returns all dependencies of a given class, direct and indirect.
+ * @param className a class name.
+ */
+ Set<String> getAllNonPageDependencies(String className);
+
/**
* Signs up this registry to invalidation events from a given hub.
*/
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
index 090d71268..fcaec091b 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
@@ -46,7 +46,9 @@ import org.apache.tapestry5.annotations.Mixins;
import org.apache.tapestry5.commons.Resource;
import org.apache.tapestry5.commons.internal.util.TapestryException;
import org.apache.tapestry5.commons.services.InvalidationEventHub;
+import org.apache.tapestry5.commons.util.UnknownValueException;
import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
import org.apache.tapestry5.internal.parser.ComponentTemplate;
import org.apache.tapestry5.internal.parser.StartComponentToken;
import org.apache.tapestry5.internal.parser.TemplateToken;
@@ -83,6 +85,8 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
private static final String META_ATTRIBUTE_SEPARATOR = ",";
+ private static final String NO_DEPENDENCY = "NONE";
+
// Key is a component, values are the components that depend on it.
final private Map<String, Set<Dependency>> map;
@@ -150,10 +154,14 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
{
final JSONObject jsonObject =
jsonArray.getJSONObject(i);
final String className = jsonObject.getString("class");
- final DependencyType dependencyType =
DependencyType.valueOf(jsonObject.getString("type"));
- final String dependency =
jsonObject.getString("dependency");
- add(className, dependency, dependencyType);
- alreadyProcessed.add(dependency);
+ final String type = jsonObject.getString("type");
+ if (!type.equals(NO_DEPENDENCY))
+ {
+ final DependencyType dependencyType =
DependencyType.valueOf(type);
+ final String dependency =
jsonObject.getString("dependency");
+ add(className, dependency, dependencyType);
+ alreadyProcessed.add(dependency);
+ }
alreadyProcessed.add(className);
}
} catch (IOException e)
@@ -179,9 +187,15 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
INVALIDATIONS_DISABLED.set(0);
});
}
-
+
@Override
public void register(Class<?> component)
+ {
+ register(component, component.getClassLoader());
+ }
+
+ @Override
+ public void register(Class<?> component, ClassLoader classLoader)
{
final String className = component.getName();
@@ -189,7 +203,7 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
Consumer<Class<?>> processClass = furtherDependencies::add;
Consumer<String> processClassName = s -> {
try {
-
furtherDependencies.add(component.getClassLoader().loadClass(s));
+ furtherDependencies.add(classLoader.loadClass(s));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
@@ -216,6 +230,7 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
{
final Class<?> dependency = field.getType();
add(component, dependency, DependencyType.INJECT_PAGE);
+ processClass.accept(dependency);
}
// @Component
@@ -245,7 +260,7 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
if (!alreadyProcessed.contains(dependencyClassName)
&&
plasticManager.shouldInterceptClassLoading(dependency.getName()))
{
- register(dependency);
+ register(dependency, classLoader);
}
}
@@ -261,14 +276,14 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
private void registerTemplate(Class<?> component, Consumer<String>
processClassName)
{
// TODO: implement caching of template dependency information, probably
- // by listening separaterly to ComponentTemplateSource to invalidate
caches
+ // by listening separately to ComponentTemplateSource to invalidate
caches
// just when template changes.
final String className = component.getName();
ComponentModel mock = new ComponentModelMock(component,
isPage(className));
final Resource templateResource =
componentTemplateLocator.locateTemplate(mock, Locale.getDefault());
String dependency;
- if (templateResource != null)
+ if (templateResource != null && templateResource.exists())
{
final ComponentTemplate template =
templateParser.parseTemplate(templateResource);
final List<TemplateToken> tokens = new LinkedList<>();
@@ -287,15 +302,34 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
String logicalName = componentToken.getComponentType();
if (logicalName != null)
{
- dependency =
resolver.resolveComponentTypeToClassName(logicalName);
- add(className, dependency, DependencyType.USAGE);
- processClassName.accept(dependency);
+ try
+ {
+ dependency =
resolver.resolveComponentTypeToClassName(logicalName);
+ add(className, dependency, DependencyType.USAGE);
+ processClassName.accept(dependency);
+ }
+ catch (UnknownValueException e)
+ {
+ // Logical name doesn't match an existing
component. Ignore
+ }
}
for (String mixin :
TapestryInternalUtils.splitAtCommas(componentToken.getMixins()))
{
- dependency =
resolver.resolveMixinTypeToClassName(mixin);
- add(className, dependency, DependencyType.USAGE);
- processClassName.accept(dependency);
+ try
+ {
+ if (mixin.contains("::"))
+ {
+ mixin = mixin.substring(0,
mixin.indexOf("::"));
+ }
+ dependency =
resolver.resolveMixinTypeToClassName(mixin);
+ add(className, dependency, DependencyType.USAGE);
+ processClassName.accept(dependency);
+ }
+ catch (UnknownValueException e)
+ {
+ // Mixin name doesn't match an existing mixin.
Ignore
+ }
+
}
}
}
@@ -527,15 +561,21 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
@Override
public Set<String> getDependents(String className)
{
+
+ ensureClassIsAlreadyProcessed(className);
+
final Set<Dependency> dependents = map.get(className);
return dependents != null
? dependents.stream().map(d ->
d.className).collect(Collectors.toSet())
: Collections.emptySet();
}
-
+
@Override
public Set<String> getDependencies(String className, DependencyType type)
{
+
+ ensureClassIsAlreadyProcessed(className);
+
Set<String> dependencies = Collections.emptySet();
if (alreadyProcessed.contains(className))
{
@@ -548,7 +588,30 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
return dependencies;
}
+ @Override
+ public Set<String> getAllNonPageDependencies(String className)
+ {
+ final Set<String> dependencies = new HashSet<>();
+ getAllNonPageDependencies(className, dependencies);
+ // Just in case, since it's possible to have circular dependencies.
+ dependencies.remove(className);
+ return Collections.unmodifiableSet(dependencies);
+ }
+
+ private void getAllNonPageDependencies(String className, Set<String>
dependencies)
+ {
+ Set<String> theseDependencies = new HashSet<>();
+ theseDependencies.addAll(getDependencies(className,
DependencyType.USAGE));
+ theseDependencies.addAll(getDependencies(className,
DependencyType.SUPERCLASS));
+ theseDependencies.removeAll(dependencies);
+ dependencies.addAll(theseDependencies);
+ for (String dependency : theseDependencies)
+ {
+ getAllNonPageDependencies(dependency, dependencies);
+ }
+ }
+
private boolean contains(Set<Dependency> dependencies, String className,
DependencyType type)
{
boolean contains = false;
@@ -667,6 +730,7 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
JSONArray jsonArray = new JSONArray();
for (String className : classNames)
{
+ boolean hasDependencies = false;
for (DependencyType dependencyType :
DependencyType.values())
{
final Set<String> dependencies =
getDependencies(className, dependencyType);
@@ -677,6 +741,20 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
object.put("type", dependencyType.name());
object.put("dependency", dependency);
jsonArray.add(object);
+ hasDependencies = true;
+ }
+ }
+ // Add a fake dependency so classes without dependencies
+ // nor classes depending on it are properly stored and
+ // retrieved, thus avoiding these classes getting into the
+ // unknown page classloader context.
+ if (!hasDependencies)
+ {
+ if (getDependents(className).isEmpty()) {
+ JSONObject object = new JSONObject();
+ object.put("class", className);
+ object.put("type", NO_DEPENDENCY);
+ jsonArray.add(object);
}
}
}
@@ -742,6 +820,20 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
}
}
+ private void ensureClassIsAlreadyProcessed(String className) {
+ if (!contains(className))
+ {
+ ThrowawayClassLoader classLoader = new
ThrowawayClassLoader(getClass().getClassLoader());
+ try
+ {
+ register(classLoader.loadClass(className));
+ } catch (ClassNotFoundException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
/**
* Only really implemented method is {@link
ComponentModel#getBaseResource()}
*/
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 8a5344c72..4d6b421d6 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
@@ -356,7 +356,23 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
public Instantiator getInstantiator(final String className)
{
- return classToInstantiator.computeIfAbsent(className,
this::createInstantiatorForClass);
+ Instantiator instantiator;
+ if (multipleClassLoaders)
+ {
+ instantiator = classToInstantiator.get(className);
+
+ if (instantiator == null)
+ {
+ instantiator = createInstantiatorForClass(className);
+ classToInstantiator.put(className, instantiator);
+ }
+
+ }
+ else
+ {
+ instantiator = classToInstantiator.computeIfAbsent(className,
this::createInstantiatorForClass);
+ }
+ return instantiator;
}
private static final ThreadLocal<Set<String>> OPEN_INSTANTIATORS =
@@ -376,6 +392,34 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
OPEN_INSTANTIATORS.get().add(className);
componentDependencyRegistry.disableInvalidations();
+
+ // Make sure the dependencies have been processed in
case
+ // there was some invalidation going on and they're
not there.
+
+ if (multipleClassLoaders)
+ {
+
+ final Set<String> dependencies = new HashSet<>();
+ dependencies.addAll(
+
componentDependencyRegistry.getDependencies(className, DependencyType.USAGE));
+ dependencies.addAll(
+
componentDependencyRegistry.getDependencies(className,
DependencyType.SUPERCLASS));
+ for (String dependency : dependencies)
+ {
+ if
(!OPEN_INSTANTIATORS.get().contains(dependency))
+ {
+ if (multipleClassLoaders)
+ {
+ getInstantiator(dependency);
+ }
+ else
+ {
+ createInstantiatorForClass(dependency);
+ }
+ }
+ }
+ }
+
PageClassLoaderContext context;
try
{
@@ -386,20 +430,18 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
componentDependencyRegistry.enableInvalidations();
}
- // Make sure the dependencies have been processed in
case
- // there was some invalidation going on and they're
not there.
-
- // TODO: maybe we need superclasses here too?
- final Set<String> dependencies =
componentDependencyRegistry.getDependencies(className, DependencyType.USAGE);
- for (String dependency : dependencies)
+ ClassInstantiator<Component> plasticInstantiator;
+ try
{
- if (!OPEN_INSTANTIATORS.get().contains(dependency))
+ plasticInstantiator =
context.getPlasticManager().getClassInstantiator(className);
+ if (multipleClassLoaders)
{
- createInstantiatorForClass(dependency);
+
context.getPlasticManager().getClassLoader().loadClass(className);
}
+ } catch (Exception e) {
+//
System.out.println(pageClassLoaderContextManager.getRoot().toRecursiveString());
+ throw new RuntimeException(e);
}
-
- ClassInstantiator<Component> plasticInstantiator =
context.getPlasticManager().getClassInstantiator(className);
final ComponentModel model =
classToModel.get(className);
OPEN_INSTANTIATORS.get().remove(className);
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
index 9c01847b3..dcbfbc47e 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java
@@ -14,6 +14,11 @@
package org.apache.tapestry5.internal.services;
+import java.util.Set;
+
+import org.apache.tapestry5.SymbolConstants;
+import
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
+import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.services.ComponentClassResolver;
@@ -22,15 +27,55 @@ public class ComponentModelSourceImpl implements
ComponentModelSource
private final ComponentClassResolver resolver;
private final ComponentInstantiatorSource source;
+
+ private final ComponentDependencyRegistry componentDependencyRegistry;
+
+ private final PageSource pageSource;
+
+ private final boolean multipleClassLoaders;
- public ComponentModelSourceImpl(ComponentClassResolver resolver,
ComponentInstantiatorSource source)
+ public ComponentModelSourceImpl(ComponentClassResolver resolver,
ComponentInstantiatorSource source,
+ ComponentDependencyRegistry componentDependencyRegistry,
+ PageSource pageSource,
+ @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+ @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean
multipleClassLoaders)
{
this.resolver = resolver;
this.source = source;
+ this.componentDependencyRegistry = componentDependencyRegistry;
+ this.pageSource = pageSource;
+ this.multipleClassLoaders = !productionMode && multipleClassLoaders;
}
public ComponentModel getModel(String componentClassName)
{
+ if (multipleClassLoaders && isPage(componentClassName))
+ {
+
+ final Set<String> superclasses =
componentDependencyRegistry.getDependencies(
+ componentClassName, DependencyType.SUPERCLASS);
+
+ if (!superclasses.isEmpty())
+ {
+ final String superclass = superclasses.iterator().next();
+ if (isPage(superclass))
+ {
+ getModel(superclass);
+ try
+ {
+
pageSource.getPage(resolver.getLogicalName(componentClassName));
+ }
+ catch (IllegalStateException e)
+ {
+ // This can be thrown in PageSourceImpl in case an
+ // infinite method call recursion is detected. In
+ // that case, the page instance is already created,
+ // so the objective of the line above is already
+ // fulfilled and we can safely ignore the exception
+ }
+ }
+ }
+ }
return source.getInstantiator(componentClassName).getModel();
}
@@ -38,4 +83,9 @@ public class ComponentModelSourceImpl implements
ComponentModelSource
{
return getModel(resolver.resolvePageNameToClassName(pageName));
}
+
+ private boolean isPage(String componentClassName)
+ {
+ return componentClassName.contains(".pages.");
+ }
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
index 6c67fe802..38ab835fd 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2010, 2011, 2012, 2023, 2024 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.
@@ -15,6 +15,7 @@
package org.apache.tapestry5.internal.services;
import java.lang.ref.SoftReference;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -29,6 +30,7 @@ import
org.apache.tapestry5.commons.services.InvalidationEventHub;
import org.apache.tapestry5.commons.util.CollectionFactory;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
import
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
import org.apache.tapestry5.internal.structure.ComponentPageElement;
@@ -106,6 +108,11 @@ public class PageSourceImpl implements PageSource
}
private final Map<CachedPageKey, Object> pageCache =
CollectionFactory.newConcurrentMap();
+
+ private final Map<String, Boolean> abstractClassInfoCache =
CollectionFactory.newConcurrentMap();
+
+ private final static ThreadLocal<String> CURRENT_PAGE =
+ ThreadLocal.withInitial(() -> null);
public PageSourceImpl(PageLoader pageLoader,
ComponentRequestSelectorAnalyzer selectorAnalyzer,
ComponentDependencyRegistry componentDependencyRegistry,
@@ -174,19 +181,39 @@ public class PageSourceImpl implements PageSource
final String className =
componentClassResolver.resolvePageNameToClassName(canonicalPageName);
if (multipleClassLoaders)
{
+
+ if (canonicalPageName.equals(CURRENT_PAGE.get()))
+ {
+ throw new IllegalStateException("Infinite method loop
detected. Bailing out.");
+ }
+ else
+ {
+ CURRENT_PAGE.set(canonicalPageName);
+ }
// Avoiding problems in PlasticClassPool.createTransformation()
// when the class being loaded has a page superclass
- final List<String> pageDependencies =
preprocessPageDependencies(className);
+ final List<String> pageDependencies =
getPageDependencies(className);
- for (String pageClassName : pageDependencies)
+ for (String dependencyClassName : pageDependencies)
{
// Avoiding infinite recursion caused by circular
dependencies
- if (!alreadyProcessed.contains(pageClassName))
+ if (!alreadyProcessed.contains(dependencyClassName))
{
- alreadyProcessed.add(pageClassName);
- page =
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName),
- invalidateUnknownContext, alreadyProcessed);
+ alreadyProcessed.add(dependencyClassName);
+
+ // Avoiding infinite recursion when, through component
overriding,
+ // a dependency resolves to the same canonical page
name as the
+ // one already requested in this call.
+ final String dependencyPageName =
componentClassResolver.resolvePageClassNameToPageName(dependencyClassName);
+ final String resolvedDependencyPageClass =
componentClassResolver.resolvePageNameToClassName(dependencyPageName);
+ if (!canonicalPageName.equals(dependencyPageName)
+ &&
!className.equals(resolvedDependencyPageClass)
+ && !isAbstract(dependencyClassName))
+ {
+ page = getPage(dependencyPageName,
+ invalidateUnknownContext,
alreadyProcessed);
+ }
}
}
@@ -221,7 +248,7 @@ public class PageSourceImpl implements PageSource
if (invalidateUnknownContext)
{
pageClassLoaderContextManager.invalidateAndFireInvalidationEvents(context);
- preprocessPageDependencies(className);
+ getPageDependencies(className);
}
context.getClassNames().clear();
// Avoiding bad invalidations
@@ -234,7 +261,7 @@ public class PageSourceImpl implements PageSource
}
- private List<String> preprocessPageDependencies(final String className) {
+ private List<String> getPageDependencies(final String className) {
final List<String> pageDependencies = new ArrayList<>();
pageDependencies.addAll(
new
ArrayList<String>(componentDependencyRegistry.getDependencies(className,
DependencyType.INJECT_PAGE)));
@@ -244,48 +271,16 @@ public class PageSourceImpl implements PageSource
final Iterator<String> iterator = pageDependencies.iterator();
while (iterator.hasNext())
{
- if (!iterator.next().contains(".pages."))
+ final String dependency = iterator.next();
+ if (!dependency.contains(".pages.") &&
!dependency.equals(className))
{
iterator.remove();
}
}
- preprocessPageClassLoaderContexts(className, pageDependencies);
return pageDependencies;
}
- private void preprocessPageClassLoaderContexts(String className, final
List<String> pageDependencies) {
- for (int i = 0; i < 5; i++)
- {
- pageClassLoaderContextManager.get(className);
- for (String pageClassName : pageDependencies)
- {
- final PageClassLoaderContext context =
pageClassLoaderContextManager.get(pageClassName);
- if (i == 1)
- {
- try
- {
- context.getClassLoader().loadClass(pageClassName);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
- }
- }
-
- // TODO: remove
-// for (String pageClassName : pageDependencies)
-// {
-// try
-// {
-//
pageClassLoaderContextManager.get(pageClassName).getClassLoader().loadClass(pageClassName);
-// } catch (ClassNotFoundException e)
-// {
-// throw new RuntimeException(e);
-// }
-// }
- }
-
@PostInjection
public void setupInvalidation(@ComponentClasses InvalidationEventHub
classesHub,
@ComponentTemplates InvalidationEventHub
templatesHub,
@@ -332,6 +327,7 @@ public class PageSourceImpl implements PageSource
iterator.remove();
}
}
+ abstractClassInfoCache.remove(className);
}
}
}
@@ -372,4 +368,11 @@ public class PageSourceImpl implements PageSource
return page;
}
+ private boolean isAbstract(final String className)
+ {
+ final Boolean computeIfAbsent =
abstractClassInfoCache.computeIfAbsent(className,
+ (s) ->
Modifier.isAbstract(ThrowawayClassLoader.load(className).getModifiers()));
+ return computeIfAbsent;
+ }
+
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
index c4985b510..c2938bf1e 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/DashboardModule.java
@@ -14,12 +14,14 @@
package org.apache.tapestry5.modules;
+import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.commons.OrderedConfiguration;
import
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
import
org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGeneratorImpl;
import org.apache.tapestry5.internal.services.dashboard.DashboardManagerImpl;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.dashboard.DashboardManager;
import org.apache.tapestry5.services.dashboard.DashboardTab;
@@ -32,11 +34,17 @@ public class DashboardModule
}
@Contribute(DashboardManager.class)
- public static void defaultTabs(OrderedConfiguration<DashboardTab>
configuration)
+ public static void defaultTabs(OrderedConfiguration<DashboardTab>
configuration,
+ @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+ @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean
multipleClassLoaders)
{
configuration.add("Pages", new DashboardTab("Pages",
"core/PageCatalog"));
configuration.add("Services", new DashboardTab("Services",
"core/ServiceStatus"));
configuration.add("Libraries", new DashboardTab("ComponentLibraries",
"core/ComponentLibraries"));
configuration.add("PageDependencyGraph", new
DashboardTab("PageDependencyGraph", "core/PageDependencyGraph"));
+ if (!productionMode && multipleClassLoaders)
+ {
+ configuration.add("PageClassLoaderContexts", new
DashboardTab("PageClassLoaderContexts", "core/PageClassLoaderContexts"));
+ }
}
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
index ae6e76a77..d97fc677a 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
@@ -47,6 +47,7 @@ import org.apache.tapestry5.services.pageload.PagePreloader;
import org.apache.tapestry5.services.pageload.PreloaderMode;
import org.apache.tapestry5.services.pageload.ReferenceType;
import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
+import org.slf4j.LoggerFactory;
/**
* @since 5.3
@@ -97,26 +98,23 @@ public class PageLoadModule
{
if (!productionMode && multipleClassLoaders)
{
- // Preload the page activation context tree for the already known
classes
- for (int i = 0; i < 5; i++)
+ // If we have component dependency information previously stored
in
+ // a file, then we just preload the page classloader contexts.
+ // Otherwise, we gather component dependency information then
+ // preload the page classloader contexts.
+ if
(componentDependencyRegistry.isStoredDependencyInformationPresent())
{
- for (String className :
componentDependencyRegistry.getClassNames())
- {
- pageClassLoaderContextManager.get(className);
- }
+ pageClassLoaderContextManager.preloadContexts();
+ }
+ else
+ {
+ LoggerFactory.getLogger(PageClassLoaderContextManager.class)
+ .warn("If the component dependency process is taking too
long, "
+ + "consider writing its results to a file using
the "
+ + " 'Store dependency information' button "
+ + "in the /t5dashboard/pages page.");
+ pageClassLoaderContextManager.preload();
}
- }
- // Preload the dependency information for all pages
- // when in production mode. Without that, exceptions during
- // page assembly will occurr. This should add just a few
- // seconds to page initialization. If it takes too long,
- // we can create a version of preload() that accepts a boolean
- // parameter defining whether templates should be parsed or not
- // (the exception occurrs when a superclass isn't loaded
- // and transformed before a subclass)
- else if (productionMode)
- {
- pageClassLoaderContextManager.preload();
}
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContext.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContext.java
index 8ae71f5f4..8d914be9c 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContext.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContext.java
@@ -22,7 +22,6 @@ import java.util.function.Function;
import org.apache.tapestry5.commons.services.PlasticProxyFactory;
import org.apache.tapestry5.internal.plastic.PlasticClassLoader;
-import org.apache.tapestry5.internal.plastic.PlasticClassPool;
import org.apache.tapestry5.plastic.PlasticManager;
import org.apache.tapestry5.plastic.PlasticUtils;
import org.slf4j.Logger;
@@ -316,35 +315,70 @@ public class PageClassLoaderContext
PageClassLoaderContext other = (PageClassLoaderContext) obj;
return Objects.equals(name, other.name);
}
-
+
@Override
public String toString()
{
+ return toString(true);
+ }
+
+ public String toString(boolean includeClassNames)
+ {
+ final PlasticClassLoader classLoader = (PlasticClassLoader)
proxyFactory.getClassLoader();
return "PageClassloaderContext [name=" + name +
", parent=" + (parent != null ? parent.getName() : "null" ) +
- ", classLoader=" +
afterAt(proxyFactory.getClassLoader().toString()) +
- (isRoot() ? "" : ", classNames=" + classNames) +
+ ", classLoader=" + afterAt(classLoader.getClassLoaderId()) +
+ (isRoot() || !includeClassNames ? "" : ", classNames=" +
classNames) +
"]";
}
public String toRecursiveString()
+ {
+ return toRecursiveString(true);
+ }
+
+ public String toRecursiveString(boolean outputClasses)
{
StringBuilder builder = new StringBuilder();
- toRecursiveString(builder, "");
+ toRecursiveString(builder, "", outputClasses);
return builder.toString();
}
- private void toRecursiveString(StringBuilder builder, String tabs)
+ public final boolean isEqualOrAncestor(PageClassLoaderContext
dependencyContext)
+ {
+ boolean equalOrAncestor = this.equals(dependencyContext);
+ if (!equalOrAncestor)
+ {
+ PageClassLoaderContext parent = this.getParent();
+ while (parent != null && !equalOrAncestor)
+ {
+ equalOrAncestor = parent.equals(dependencyContext);
+ if (equalOrAncestor)
+ {
+ break;
+ }
+ else
+ {
+ parent = parent.getParent();
+ }
+ }
+ }
+ return equalOrAncestor;
+ }
+
+ private void toRecursiveString(StringBuilder builder, String tabs, boolean
outputClasses)
{
builder.append(tabs);
builder.append(name);
builder.append(" : ");
builder.append(afterAt(proxyFactory.getClassLoader().toString()));
- builder.append(" : ");
- builder.append(classNames);
+ if (outputClasses) {
+ builder.append(" : ");
+ builder.append(classNames);
+ }
builder.append("\n");
for (PageClassLoaderContext child : children) {
- child.toRecursiveString(builder, tabs + "\t");
+ child.toRecursiveString(builder, tabs + "\t", outputClasses);
}
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
index b37f0a0fc..fa91b70f3 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManager.java
@@ -95,5 +95,11 @@ public interface PageClassLoaderContextManager
* page classloader contexts.
*/
void preload();
+
+ /**
+ * Preloads the page classloader contexts.
+ * @since 5.8.7
+ */
+ void preloadContexts();
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
index afe8a6746..6b83ad044 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassLoaderContextManagerImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2023 The Apache Software Foundation
+// Copyright 2023, 2024 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.
@@ -15,6 +15,7 @@ package org.apache.tapestry5.services.pageload;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -28,6 +29,7 @@ import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.commons.internal.util.TapestryException;
import org.apache.tapestry5.commons.services.InvalidationEventHub;
import org.apache.tapestry5.commons.services.PlasticProxyFactory;
+import org.apache.tapestry5.internal.ThrowawayClassLoader;
import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
import
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
import
org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub;
@@ -66,10 +68,14 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
private static final AtomicInteger MERGED_COUNTER = new AtomicInteger(1);
+ private final static ThreadLocal<AtomicInteger> CONTEXTS_CREATED =
ThreadLocal.withInitial(AtomicInteger::new);
+
private Function<ClassLoader, PlasticProxyFactory>
plasticProxyFactoryProvider;
private PageClassLoaderContext root;
+ private boolean preloadingContexts;
+
public PageClassLoaderContextManagerImpl(
final ComponentDependencyRegistry componentDependencyRegistry,
final ComponentClassResolver componentClassResolver,
@@ -83,7 +89,7 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
this.componentClassResolver = componentClassResolver;
this.invalidationHub = invalidationHub;
this.componentClassesInvalidationEventHub =
componentClassesInvalidationEventHub;
- this.multipleClassLoaders = multipleClassLoaders;
+ this.multipleClassLoaders = multipleClassLoaders && !productionMode;
this.productionMode = productionMode;
invalidationHub.addInvalidationCallback(this::listen);
NESTED_MERGE_COUNT.set(0);
@@ -126,28 +132,44 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
{
PageClassLoaderContext context;
- final String enclosingClassName = getAdjustedClassName(className);
- context = root.findByClassName(enclosingClassName);
-
- if (context == null)
+ // Class isn't in a controlled package, so it doesn't get transformed
+ // and should go for the root context, which is never thrown out.
+ if (!root.getPlasticManager().shouldInterceptClassLoading(className))
+ {
+ context = root;
+ }
+ else if (productionMode || !multipleClassLoaders)
+ {
+ context = getUnknownContext(root, plasticProxyFactoryProvider);
+ }
+ else
{
- Set<String> classesToInvalidate = new HashSet<>();
- context = processUsingDependencies(
- enclosingClassName,
- root,
- () -> getUnknownContext(root, plasticProxyFactoryProvider),
- plasticProxyFactoryProvider,
- classesToInvalidate);
+ // Multiple classloader mode.
+ final String enclosingClassName = getAdjustedClassName(className);
+ context = root.findByClassName(enclosingClassName);
- if (!classesToInvalidate.isEmpty())
+ if (context == null)
{
- invalidate(classesToInvalidate);
- }
-
- if (!className.equals(enclosingClassName))
- {
- loadClass(className, context);
+ Set<String> classesToInvalidate = new HashSet<>();
+
+ context = processUsingDependencies(
+ enclosingClassName,
+ root,
+ () -> getUnknownContext(root,
plasticProxyFactoryProvider),
+ plasticProxyFactoryProvider,
+ classesToInvalidate);
+
+ if (!classesToInvalidate.isEmpty())
+ {
+ invalidate(classesToInvalidate);
+ }
+
+ if (!className.equals(enclosingClassName))
+ {
+ loadClass(enclosingClassName, context);
+ }
+
}
}
@@ -228,125 +250,128 @@ public class PageClassLoaderContextManagerImpl
implements PageClassLoaderContext
{
LOGGER.debug("Processing class {}", className);
+ alreadyProcessed.add(className);
+
+ // Sorting dependencies by type/alphabetically so we have
consistent
+ // context trees between runs of the same webapp
+ List<String> allNonPageDependencies = new ArrayList<>(
+
componentDependencyRegistry.getAllNonPageDependencies(className));
+ Collections.sort(allNonPageDependencies,
ClassNameComparator.INSTANCE);
- // Class isn't in a controlled package, so it doesn't get
transformed
- // and should go for the root context, which is never thrown out.
- if
(!root.getPlasticManager().shouldInterceptClassLoading(className))
+ List<String> dependencies = new
ArrayList<>(getDependenciesWithoutPages(className));
+ Collections.sort(dependencies, ClassNameComparator.INSTANCE);
+
+ // Process dependencies depth-first
+ do
{
- context = root;
- } else {
- if (!productionMode && (
- !componentDependencyRegistry.contains(className) ||
- !multipleClassLoaders))
- {
- context = unknownContextProvider.get();
- }
- else
+
+ // Very unlikely to have infinite loops, but lets
+ // avoid them anyway.
+ int passes = 0;
+
+ int contextsCreatedInThisPass = -1;
+
+ while (contextsCreatedInThisPass <
CONTEXTS_CREATED.get().get() && passes < 1000)
{
-
- alreadyProcessed.add(className);
-
- // Sorting dependencies alphabetically so we have
consistent results.
- List<String> dependencies = new
ArrayList<>(getDependenciesWithoutPages(className));
- Collections.sort(dependencies);
- // Process dependencies depth-first
- for (String dependency : dependencies)
+ contextsCreatedInThisPass = CONTEXTS_CREATED.get().get();
+
+ for (String dependency : allNonPageDependencies)
{
// Avoid infinite recursion loops
- if (!alreadyProcessed.contains(dependency))
+ if (!alreadyProcessed.contains(dependency)
+ || root.findByClassName(dependency) == null)
{
processUsingDependencies(dependency, root,
unknownContextProvider,
plasticProxyFactoryProvider,
classesToInvalidate, alreadyProcessed, false);
}
}
- // Collect context dependencies
- Set<PageClassLoaderContext> contextDependencies = new
HashSet<>();
- for (String dependency : dependencies)
- {
- PageClassLoaderContext dependencyContext =
root.findByClassName(dependency);
- // Avoid infinite recursion loops
- if (!alreadyProcessed.contains(dependency))
- {
- if (dependencyContext == null)
- {
- dependencyContext =
processUsingDependencies(dependency, root, unknownContextProvider,
- plasticProxyFactoryProvider,
classesToInvalidate, alreadyProcessed);
-
- }
- if (!dependencyContext.isRoot())
- {
- contextDependencies.add(dependencyContext);
- }
- }
- }
-
- if (!multipleClassLoaders)
- {
- context = root;
- }
- else if (contextDependencies.size() == 0)
- {
- context = new PageClassLoaderContext(
- getContextName(className),
- root,
- Collections.singleton(className),
-
plasticProxyFactoryProvider.apply(root.getClassLoader()),
- this::get);
- }
- else
+ }
+
+ }
+ while (!allNeededContextsAvailable(allNonPageDependencies));
+
+ // Collect context dependencies
+ Set<PageClassLoaderContext> contextDependencies = new HashSet<>();
+ for (String dependency : allNonPageDependencies)
+ {
+ PageClassLoaderContext dependencyContext =
root.findByClassName(dependency);
+ // Avoid infinite recursion loops
+ if (multipleClassLoaders ||
!alreadyProcessed.contains(dependency))
+ {
+ if (dependencyContext == null)
{
- PageClassLoaderContext parentContext;
- if (contextDependencies.size() == 1)
- {
- parentContext =
contextDependencies.iterator().next();
- }
- else
- {
- parentContext = merge(contextDependencies,
plasticProxyFactoryProvider, root, classesToInvalidate);
- }
- context = new PageClassLoaderContext(
- getContextName(className),
- parentContext,
- Collections.singleton(className),
-
plasticProxyFactoryProvider.apply(parentContext.getClassLoader()),
- this::get);
- }
+ dependencyContext =
processUsingDependencies(dependency, root, unknownContextProvider,
+ plasticProxyFactoryProvider,
classesToInvalidate, alreadyProcessed);
- if (multipleClassLoaders)
- {
- context.getParent().addChild(context);
}
-
- // Ensure non-page class is initialized in the correct
context and classloader.
- // Pages get their own context and classloader, so this
initialization
- // is both non-needed and a cause for an NPE if it happens.
- if (!componentClassResolver.isPage(className)
- ||
componentDependencyRegistry.getDependencies(className,
DependencyType.USAGE).isEmpty())
+ if (!dependencyContext.isRoot())
{
- loadClass(className, context);
+ contextDependencies.add(dependencyContext);
}
-
- if (multipleClassLoaders)
- {
- LOGGER.debug("New context: {}", context);
- }
-
}
}
+ if (contextDependencies.size() == 0)
+ {
+ context = new PageClassLoaderContext(
+ getContextName(className),
+ root,
+ Collections.singleton(className),
+
plasticProxyFactoryProvider.apply(root.getClassLoader()),
+ this::get);
+ CONTEXTS_CREATED.get().incrementAndGet();
+ }
+ else
+ {
+ PageClassLoaderContext parentContext;
+ if (contextDependencies.size() == 1)
+ {
+ parentContext = contextDependencies.iterator().next();
+ }
+ else
+ {
+ parentContext = merge(contextDependencies,
plasticProxyFactoryProvider, root, classesToInvalidate);
+ }
+ context = new PageClassLoaderContext(
+ getContextName(className),
+ parentContext,
+ Collections.singleton(className),
+
plasticProxyFactoryProvider.apply(parentContext.getClassLoader()),
+ this::get);
+ CONTEXTS_CREATED.get().incrementAndGet();
+ }
+
+ LOGGER.debug("New context: {}", context);
+
}
context.addClass(className);
+ context.getParent().addChild(context);
return context;
}
+ private boolean allNeededContextsAvailable(List<String> dependencies)
+ {
+ boolean available = true;
+ for (String dependency : dependencies)
+ {
+ if (root.findByClassName(dependency) == null)
+ {
+ available = false;
+ break;
+ }
+ }
+ return available;
+ }
+
private Set<String> getDependenciesWithoutPages(String className)
{
Set<String> dependencies = new HashSet<>();
dependencies.addAll(componentDependencyRegistry.getDependencies(className,
DependencyType.USAGE));
dependencies.addAll(componentDependencyRegistry.getDependencies(className,
DependencyType.SUPERCLASS));
+ dependencies.remove(className); // Just in case
return Collections.unmodifiableSet(dependencies);
}
@@ -387,12 +412,19 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
}
LOGGER.debug(builder.toString().trim());
}
-
+
+ final Set<String> classesToReprocess = multipleClassLoaders ? new
HashSet<>() : Collections.emptySet();
Set<PageClassLoaderContext> allContextsIncludingDescendents = new
HashSet<>();
+
for (PageClassLoaderContext context : contextDependencies)
{
+ final Set<PageClassLoaderContext> descendents =
context.getDescendents();
allContextsIncludingDescendents.add(context);
- allContextsIncludingDescendents.addAll(context.getDescendents());
+ allContextsIncludingDescendents.addAll(descendents);
+ for (PageClassLoaderContext descendent : descendents)
+ {
+ addClassNames(descendent, classesToReprocess);
+ }
}
PageClassLoaderContext merged;
@@ -458,8 +490,22 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
plasticProxyFactoryProvider.apply(parent.getClassLoader()),
this::get);
+ CONTEXTS_CREATED.get().incrementAndGet();
+
parent.addChild(merged);
+ // Recreating contexts for classes that got invalidated but
+ // aren't part of the new merged context (i.e. the classes
+ // in contexts are are descendent of the merged contexts).
+// if (!classesToReprocess.isEmpty())
+// {
+// final List<String> sorted = new ArrayList<>(classesToReprocess);
+// for (String className : sorted)
+// {
+// get(className);
+// }
+// }
+
// for (String className : classNames)
// {
// loadClass(className, merged);
@@ -578,7 +624,7 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
}
private void invalidate(Set<String> classesToInvalidate) {
- if (!classesToInvalidate.isEmpty())
+ if (!classesToInvalidate.isEmpty() && !preloadingContexts)
{
LOGGER.debug("Invalidating classes {}", classesToInvalidate);
markAsInvalidatingContext();
@@ -647,10 +693,7 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
public void preload()
{
- final PageClassLoaderContext context = new
PageClassLoaderContext(PageClassLoaderContext.UNKOWN_CONTEXT_NAME, root,
- Collections.emptySet(),
- plasticProxyFactoryProvider.apply(root.getClassLoader()),
- this::get);
+ final ClassLoader classLoader = new
ThrowawayClassLoader(PageClassLoaderContext.class.getClassLoader());
final List<String> pageNames = componentClassResolver.getPageNames();
final List<String> classNames = new ArrayList<>(pageNames.size());
@@ -664,7 +707,7 @@ public class PageClassLoaderContextManagerImpl implements
PageClassLoaderContext
try
{
final String className =
componentClassResolver.resolvePageNameToClassName(page);
-
componentDependencyRegistry.register(context.getClassLoader().loadClass(className));
+
componentDependencyRegistry.register(classLoader.loadClass(className));
classNames.add(className);
} catch (ClassNotFoundException e)
{
@@ -680,30 +723,101 @@ public class PageClassLoaderContextManagerImpl
implements PageClassLoaderContext
if (LOGGER.isInfoEnabled())
{
- LOGGER.info(String.format("Dependency information gathered in %.3f
ms", (finish - start) / 1000.0));
+ LOGGER.info(String.format("Dependency information for %d pages
gathered in %.3f s",
+ pageNames.size(), (finish - start) / 1000.0));
}
- context.invalidate();
+ preloadContexts();
+ }
+
+ @Override
+ public void preloadContexts()
+ {
+ long start;
+ long finish;
LOGGER.info("Starting preloading page classloader contexts");
start = System.currentTimeMillis();
- for (int i = 0; i < 10; i++)
+ final List<String> classNames = new
ArrayList<>(componentDependencyRegistry.getClassNames());
+ classNames.sort(ClassNameComparator.INSTANCE);
+
+ int runs = 0;
+ preloadingContexts = true;
+
+ try
{
- for (String className : classNames)
+ // The run counter check is to just avoid possible infinite loops,
+ // although that's very unlikely.
+ int contexts = -1;
+ while (runs < 5000 && contexts < CONTEXTS_CREATED.get().get())
{
- get(className);
+ runs++;
+ contexts = CONTEXTS_CREATED.get().get();
+ for (String className : classNames)
+ {
+ get(className);
+ }
}
}
+ finally
+ {
+ preloadingContexts = false;
+ }
finish = System.currentTimeMillis();
if (LOGGER.isInfoEnabled())
{
- LOGGER.info(String.format("Preloading of page classloadercontexts
finished in %.3f ms", (finish - start) / 1000.0));
+ LOGGER.info(String.format("Preloading of page classloader contexts
finished in %.3f s (%d passes)", (finish - start) / 1000.0, runs));
}
+ }
+
+ /**
+ * Sorts base classes before mixins, mixins before components and
components
+ * before pages. If both classes belong to the same type, order
alphabetically.
+ */
+ private static final class ClassNameComparator implements
Comparator<String>
+ {
+
+ private static final Comparator<String> INSTANCE = new
ClassNameComparator();
+ @Override
+ public int compare(String o1, String o2)
+ {
+ int value1 = getValue(o1);
+ int value2 = getValue(o2);
+ int comparison = value1 - value2;
+ if (comparison == 0)
+ {
+ comparison = o1.compareTo(o2);
+ }
+ return comparison;
+ }
+
+ private int getValue(String className)
+ {
+ int value;
+ if (className.contains(".base."))
+ {
+ value = 0;
+ }
+ else if (className.contains(".mixins."))
+ {
+ value = 1;
+ }
+ else if (className.contains(".components."))
+ {
+ value = 2;
+ }
+ else
+ {
+ value = 3;
+ }
+ return value;
+ }
+
}
}
diff --git
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
index e5323cb5a..a43b79659 100644
---
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
+++
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ComponentLibraries.tml
@@ -3,125 +3,180 @@
<h2 t:type="If" t:test="productionMode">This page is disabled in
production mode</h2>
- <t:if t:test="!productionMode">
- <h1><strong>${libraryMappings.size()}</strong> component libraries
used</h1>
-
-
- <ul id="libraryList" class="list-group">
- <li t:type="Loop" t:source="libraryMappings"
t:value="libraryMapping" class="list-group-item">
- <a href="#${libraryClientId}">
- <code>${libraryMapping.libraryName}</code>
<t:if test="info">: ${info.name}</t:if>
- </a>
- <p t:type="If" t:test="info?.description">
- ${info.description}
- </p>
- <p class="tags" t:type="If" t:test="!info?.tags.empty">
- Tags:
- <span t:type="Loop"
t:source="info.tags" t:value="var:tag" class="badge"
- style="margin-right: 0.3em;
font-size: 0.75em">
- ${var:tag}
- </span>
- </p>
- </li>
- </ul>
-
-
- <div id="libraries">
-
- <div class="libraryInfo" t:type="Loop"
t:source="libraryMappings" t:value="libraryMapping" id="${libraryClientId}">
-
- <h2><code>${libraryMapping.libraryName}</code>
<t:if test="info">: ${info.name}</t:if></h2>
-
- <t:if test="info">
+ <t:If t:test="!productionMode">
+ <t:zone>
- <dl class="dl-horizontal">
-
- <dt>Homepage</dt>
- <dd class="homepage">
- <t:if test="info.homepageUrl"
else="message:not-informed">
- <a
href="${info.homepageUrl}">${info.homepageUrl}</a>
- </t:if>
- </dd>
-
- <dt>Version</dt>
- <dd class="version">
- <t:if test="info.version"
else="message:not-informed">
- ${info.version}
- </t:if>
- </dd>
-
- <dt>Tapestry version</dt>
- <dd class="tapestryVersion">
- <t:if
test="info.tapestryVersion" else="message:not-informed">
- ${info.tapestryVersion}
- </t:if>
- </dd>
+ <p>
+ <t:if test="showEverything">
+ <p:then>
+ <t:eventLink
event="showRestricted" zone="^">
+ Don't show webapp
library
+ </t:eventLink>
+ </p:then>
+ <p:else>
+ <t:eventLink
event="showEverything" zone="^">
+ Show webapp library
+ </t:eventLink>
+ </p:else>
+ </t:if>
+ </p>
+
+ <t:If test="!selectedComponent">
+
+ <h1><strong>${libraryMappings.size()}</strong>
component libraries used</h1>
+
+ <ul id="libraryList" class="list-group">
+ <li t:type="Loop" t:source="libraryMappings"
t:value="libraryMapping" class="list-group-item">
+ <a href="#${libraryClientId}">
+ <code>${libraryName}</code>
<t:if test="info">: ${info.name}</t:if>
+ </a>
+ <p t:type="If"
t:test="info?.description">
+ ${info.description}
+ </p>
+ <p class="tags" t:type="If"
t:test="!info?.tags.empty">
+ Tags:
+ <span t:type="Loop"
t:source="info.tags" t:value="var:tag" class="badge"
+
style="margin-right: 0.3em; font-size: 0.75em">
+ ${var:tag}
+ </span>
+ </p>
+ </li>
+ </ul>
+
+
+ <div id="libraries">
- <dt>Documentation URL</dt>
- <dd class="documentationUrl">
- <t:if
test="info.documentationUrl" else="message:not-informed">
- <a
href="${info.documentationUrl}">${info.documentationUrl}</a>
- </t:if>
- </dd>
-
- <dt>JavaDoc URL</dt>
- <dd class="javadocUrl">
- <t:if test="info.javadocUrl"
else="message:not-informed">
- <a
href="${info.javadocUrl}">${info.javadocUrl}</a>
- </t:if>
- </dd>
-
- <t:if
test="info.dependencyManagementInfoPresent">
- <dt>Dependency information</dt>
- <dd
class="dependencyInformation">
- Group id <code
class="groupId">${info.groupId}</code>,
- artifact id <code
class="groupId">${info.artifactId}</code>,
- version <code
class="groupId">${info.version}</code>
- <br/>
- <a
href="${info.dependencyManagementInfoUrl}"
- target="_blank">
- <em>More
information at Maven Central Respository</em>
- </a>
- </dd>
- </t:if>
-
- <dt>Source browse URL</dt>
- <dd class="sourceBrowseUrl">
- <t:if
test="info.sourceBrowseUrl" else="message:not-informed">
- <a
href="${info.sourceBrowseUrl}">${info.sourceBrowseUrl}</a>
- </t:if>
- </dd>
-
- <dt>Issue tracker URL</dt>
- <dd class="issueTrackerUrl">
- <t:if
test="info.issueTrackerUrl" else="message:not-informed">
- <a
href="${info.issueTrackerUrl}">${info.issueTrackerUrl}</a>
- </t:if>
- </dd>
-
- <dt></dt>
- <dd class="jsonDescription">
- <a t:type="EventLink"
t:event="json" t:context="libraryMapping.libraryName">Generate JSON
description</a>
- </dd>
+ <div class="libraryInfo" t:type="Loop"
t:source="libraryMappings" t:value="libraryMapping" id="${libraryClientId}">
- </dl>
-
- </t:if>
+ <h2><code>${libraryName}</code>
<t:if test="info">: ${info.name}</t:if></h2>
+
+ <t:if test="info">
+
+ <dl class="dl-horizontal">
+
+ <dt>Homepage</dt>
+ <dd class="homepage">
+ <t:if
test="info.homepageUrl" else="message:not-informed">
+ <a
href="${info.homepageUrl}">${info.homepageUrl}</a>
+ </t:if>
+ </dd>
+
+ <dt>Version</dt>
+ <dd class="version">
+ <t:if
test="info.version" else="message:not-informed">
+
${info.version}
+ </t:if>
+ </dd>
+
+ <dt>Tapestry
version</dt>
+ <dd
class="tapestryVersion">
+ <t:if
test="info.tapestryVersion" else="message:not-informed">
+
${info.tapestryVersion}
+ </t:if>
+ </dd>
+
+ <dt>Documentation
URL</dt>
+ <dd
class="documentationUrl">
+ <t:if
test="info.documentationUrl" else="message:not-informed">
+ <a
href="${info.documentationUrl}">${info.documentationUrl}</a>
+ </t:if>
+ </dd>
+
+ <dt>JavaDoc URL</dt>
+ <dd class="javadocUrl">
+ <t:if
test="info.javadocUrl" else="message:not-informed">
+ <a
href="${info.javadocUrl}">${info.javadocUrl}</a>
+ </t:if>
+ </dd>
+
+ <t:if
test="info.dependencyManagementInfoPresent">
+ <dt>Dependency
information</dt>
+ <dd
class="dependencyInformation">
+ Group
id <code class="groupId">${info.groupId}</code>,
+
artifact id <code class="groupId">${info.artifactId}</code>,
+ version
<code class="groupId">${info.version}</code>
+ <br/>
+ <a
href="${info.dependencyManagementInfoUrl}"
+
target="_blank">
+
<em>More information at Maven Central Respository</em>
+ </a>
+ </dd>
+ </t:if>
+
+ <dt>Source browse
URL</dt>
+ <dd
class="sourceBrowseUrl">
+ <t:if
test="info.sourceBrowseUrl" else="message:not-informed">
+ <a
href="${info.sourceBrowseUrl}">${info.sourceBrowseUrl}</a>
+ </t:if>
+ </dd>
+
+ <dt>Issue tracker
URL</dt>
+ <dd
class="issueTrackerUrl">
+ <t:if
test="info.issueTrackerUrl" else="message:not-informed">
+ <a
href="${info.issueTrackerUrl}">${info.issueTrackerUrl}</a>
+ </t:if>
+ </dd>
+
+ <dt></dt>
+ <dd
class="jsonDescription">
+ <a
t:type="EventLink" t:event="json"
t:context="libraryMapping.libraryName">Generate JSON description</a>
+ </dd>
+
+ </dl>
+
+ </t:if>
+
+ <p t:type="If" t:test="!info"
class="noInformation">No additional information provided for
<code>${libraryMapping.libraryName}</code>.</p>
+
+ <!-- <div t:type="Zone" t:id="pages"
id="prop:libraryClientZoneClientId"> -->
+ <!-- </div> -->
+
+ <div t:type="Delegate"
to="componentsTable"></div>
+ <div t:type="Delegate"
to="pagesTable"></div>
+ <div t:type="Delegate"
to="mixinsTable"></div>
+
+ </div>
+
+ </div>
- <p t:type="If" t:test="!info"
class="noInformation">No additional information provided for
<code>${libraryMapping.libraryName}</code>.</p>
+ </t:If>
- <!-- <div t:type="Zone" t:id="pages"
id="prop:libraryClientZoneClientId"> -->
- <!-- </div> -->
-
- <div t:type="Delegate"
to="componentsTable"></div>
- <div t:type="Delegate" to="pagesTable"></div>
- <div t:type="Delegate" to="mixinsTable"></div>
+ <t:If test="selectedComponent">
+ <h1><strong>${selectedComponent}</strong>
(${componentClassName})</h1>
- </div>
-
- </div>
-
- </t:if>
+ <p>
+ <t:eventLink event="reset"
zone="^">Back to component listing</t:eventLink>
+ </p>
+
+ <div class="panel panel-default vert-offset">
+ <div class="panel-heading">Component dependency
information for ${selectedComponent} (just direct dependencies)</div>
+ <div class="panel-body">
+ <ul>
+ <li t:type="Loop"
t:value="dependency" t:source="dependencies">
+ ${displayLogicalName}
(${dependency})
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="panel panel-default vert-offset">
+ <div class="panel-heading">Components depending
on ${selectedComponent} (just direct dependencies)</div>
+ <div class="panel-body">
+ <ul>
+ <li t:type="Loop"
t:value="dependency" t:source="dependents">
+ ${displayLogicalName}
(${dependency})
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="panel panel-default vert-offset">
+ <div
class="panel-heading">${selectedComponent}'s dependency tree</div>
+ <div class="panel-body">
+ <t:graphviz value="graphvizValue"
showSource="true"/>
+ </div>
+ </div>
+ </t:If>
+ </t:zone>
+ </t:If>
<t:block id="classesTable">
<div t:type="If" t:test="!logicalNames.empty">
@@ -138,7 +193,11 @@
</thead>
<tbody>
<tr t:type="Loop"
t:source="logicalNames" t:value="logicalName">
-
<td><code>${simpleLogicalName}</code></td>
+ <td>
+ <t:eventLink
event="selectComponent" t:context="context" t:zone="^">
+
<code>${simpleLogicalName}</code>
+ </t:eventLink>
+ </td>
<td>${description?.text()}</td>
<td>
<ul t:type="If"
t:test="classHasTags" style="padding: 0; margin: 0;">
@@ -163,5 +222,5 @@
</table>
</div>
</t:block>
-
+
</t:block>
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 bdac0f8b1..2eea91036 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
@@ -77,7 +77,7 @@
<div class="panel panel-default vert-offset" t:type="If"
t:test="selectedPage">
<div class="panel-heading">${selectedPage.name}'s
dependency tree</div>
<div class="panel-body">
- <t:graphviz value="graphvizValue"/>
+ <t:graphviz value="graphvizValue"
showSource="true"/>
</div>
</div>
</t:if>
diff --git
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
new file mode 100644
index 000000000..8e39c0f0e
--- /dev/null
+++
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageClassLoaderContexts.tml
@@ -0,0 +1,17 @@
+<t:block id="content"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
+ xmlns:p="tapestry:parameter">
+ <h1>Page classloader contexts <t:glyphicon name="list" /></h1>
+ <style>
+ #tree li {
+ display: block;
+ margin-top: 0.25em;
+ line-height: 2m;
+ }
+ #tree .glyphicon {
+ margin-right: 0.25em;
+ }
+ </style>
+ <ul id="tree">
+ <t:trigger event="render"/>
+ </ul>
+</t:block>
\ No newline at end of file
diff --git a/tapestry-core/src/test/app1/AtComponentType.tml
b/tapestry-core/src/test/app1/AtComponentType.tml
new file mode 100644
index 000000000..50a2bf92e
--- /dev/null
+++ b/tapestry-core/src/test/app1/AtComponentType.tml
@@ -0,0 +1,6 @@
+<t:border xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"
+ xmlns:p="tapestry:parameter">
+
+ <input t:id="input"/>
+
+</t:border>
\ No newline at end of file
diff --git a/tapestry-core/src/test/app1/GridDemo.tml
b/tapestry-core/src/test/app1/GridDemo.tml
index 5a4f12eb7..650ecf163 100644
--- a/tapestry-core/src/test/app1/GridDemo.tml
+++ b/tapestry-core/src/test/app1/GridDemo.tml
@@ -1,6 +1,6 @@
<html t:type="Border"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
- <h1>Grid Demo</h1>
+ <h1>Grid Demo!!!!</h1>
<table t:type="grid" t:id="grid" source="tracks" row="track">
<t:parameter name="ratingheader">
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
new file mode 100644
index 000000000..de0627822
--- /dev/null
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/AtComponentType.java
@@ -0,0 +1,24 @@
+// Copyright 2024 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.integration.app1.pages;
+
+import org.apache.tapestry5.Field;
+import org.apache.tapestry5.annotations.Component;
+
+public class AtComponentType
+{
+ @Component(type = "textfield")
+ private Field input;
+}
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
index 1d064920d..83faa8be5 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GridDemo.java
@@ -14,6 +14,7 @@
package org.apache.tapestry5.integration.app1.pages;
+import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Grid;
@@ -48,4 +49,9 @@ public class GridDemo
{
grid.getSortModel().updateSort("rating");
}
+
+// public void afterRender(MarkupWriter writer)
+// {
+// writer.getDocument().find("html/body").elementAt(1,
"p").text("bbbbb");
+// }
}
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
index 8b89e7216..9c535be69 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
@@ -19,17 +19,22 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
+import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.tapestry5.commons.MappedConfiguration;
+import org.apache.tapestry5.commons.Resource;
import org.apache.tapestry5.corelib.base.AbstractComponentEventLink;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.corelib.base.AbstractLink;
@@ -52,6 +57,7 @@ import org.apache.tapestry5.corelib.components.Loop;
import org.apache.tapestry5.corelib.components.Output;
import org.apache.tapestry5.corelib.components.OutputRaw;
import org.apache.tapestry5.corelib.components.PageLink;
+import org.apache.tapestry5.corelib.components.PasswordField;
import org.apache.tapestry5.corelib.components.PropertyDisplay;
import org.apache.tapestry5.corelib.components.PropertyEditor;
import org.apache.tapestry5.corelib.components.RenderObject;
@@ -61,9 +67,7 @@ import org.apache.tapestry5.corelib.components.TextOutput;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.corelib.mixins.FormGroup;
import org.apache.tapestry5.corelib.mixins.RenderDisabled;
-import org.apache.tapestry5.corelib.pages.PropertyEditBlocks;
import org.apache.tapestry5.integration.app1.base.BaseLayoutPage;
-import org.apache.tapestry5.integration.app1.base.EmptyExtendTemplate;
import org.apache.tapestry5.integration.app1.components.Border;
import org.apache.tapestry5.integration.app1.components.ErrorComponent;
import org.apache.tapestry5.integration.app1.components.OuterAny;
@@ -75,15 +79,17 @@ import
org.apache.tapestry5.integration.app1.mixins.EchoValue;
import org.apache.tapestry5.integration.app1.mixins.EchoValue2;
import org.apache.tapestry5.integration.app1.mixins.TextOnlyOnDisabled;
import org.apache.tapestry5.integration.app1.pages.AlertsDemo;
+import org.apache.tapestry5.integration.app1.pages.AtComponentType;
import org.apache.tapestry5.integration.app1.pages.BlockCaller;
import org.apache.tapestry5.integration.app1.pages.BlockHolder;
-import
org.apache.tapestry5.integration.app1.pages.EmbeddedComponentTypeConflict;
import org.apache.tapestry5.integration.app1.pages.InstanceMixinDependencies;
import org.apache.tapestry5.integration.app1.pages.MixinParameterDefault;
-import org.apache.tapestry5.integration.app1.pages.TemplateOverrideDemo;
import
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
import org.apache.tapestry5.internal.services.templates.DefaultTemplateLocator;
+import org.apache.tapestry5.internal.services.templates.PageTemplateLocator;
import org.apache.tapestry5.ioc.internal.QuietOperationTracker;
+import org.apache.tapestry5.ioc.internal.util.AbstractResource;
+import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.modules.TapestryModule;
import org.apache.tapestry5.plastic.PlasticManager;
import org.apache.tapestry5.services.ComponentClassResolver;
@@ -96,6 +102,7 @@ import org.testng.annotations.Test;
/**
* Tests {@link ComponentDependencyRegistryImpl}.
*/
+@SuppressWarnings("deprecation")
public class ComponentDependencyRegistryImplTest
{
@@ -123,11 +130,37 @@ public class ComponentDependencyRegistryImplTest
TapestryModule.contributeTemplateParser(templateConfiguration);
templateParser = new TemplateParserImpl(templateConfiguration.map,
false, new QuietOperationTracker());
- componentTemplateLocator = new DefaultTemplateLocator();
+ final String rootFolder = "src/test/app1/";
+ final File folderFile = new File(rootFolder);
+ final Resource app1ContextFolderResource = new FileResource("/",
folderFile);
+
+ final PageTemplateLocator pageTemplateLocator = new
PageTemplateLocator(app1ContextFolderResource, resolver, "");
+ final DefaultTemplateLocator defaultTemplateLocator = new
DefaultTemplateLocator();
+ componentTemplateLocator = new ComponentTemplateLocator()
+ {
+ @Override
+ public Resource locateTemplate(ComponentModel model, Locale
locale)
+ {
+ Resource resource =
defaultTemplateLocator.locateTemplate(model, locale);
+ if (resource == null)
+ {
+ resource = pageTemplateLocator.locateTemplate(model,
locale);
+ }
+ return resource != null && resource.exists() ? resource : null;
+ }
+ };
resolver = EasyMock.createMock(ComponentClassResolver.class);
+
EasyMock.expect(resolver.resolvePageClassNameToPageName(EasyMock.anyString()))
+ .andAnswer(() -> {
+ String s = ((String) EasyMock.getCurrentArguments()[0]);
+ final String pageName = s.substring(s.lastIndexOf('.') +
1);
+ return pageName;
+ }).anyTimes();
+
expectResolveComponent(TextField.class);
+ expectResolveComponent(PasswordField.class);
expectResolveComponent(Border.class);
expectResolveComponent(BeanEditForm.class);
expectResolveComponent(Zone.class);
@@ -163,8 +196,6 @@ public class ComponentDependencyRegistryImplTest
EasyMock.expect(resolver.resolveMixinTypeToClassName("formgroup"))
.andReturn(FormGroup.class.getName()).anyTimes();
- // TODO: remove this
-//
EasyMock.expect(resolver.getLogicalName(EasyMock.anyString())).andAnswer(() ->
(String) EasyMock.getCurrentArguments()[0]).anyTimes();
EasyMock.expect(resolver.isPage(EasyMock.anyString())).andAnswer(() ->
{
String string = (String) EasyMock.getCurrentArguments()[0];
return string.contains(".pages.");
@@ -351,7 +382,40 @@ public class ComponentDependencyRegistryImplTest
}
- // Tested code isn't being used at the moment
+ @Test
+ public void get_all_non_page_dependencies()
+ {
+
+ final String page = "page";
+ final String className = "root";
+ final String superclass = "superclass";
+ final Set<String> expectedAllNonPageDependencies = new HashSet<>();
+ final BiConsumer<String, String> add = (c, d) -> {
+ expectedAllNonPageDependencies.add(d);
+ add(c, d, DependencyType.USAGE);
+ };
+
+ add(className, page, DependencyType.INJECT_PAGE);
+
+ add(className, superclass, DependencyType.SUPERCLASS);
+ expectedAllNonPageDependencies.add(superclass);
+
+ add.accept(className, "child1");
+ add.accept(className, "child2");
+ add.accept("child1", "child1.1");
+ add.accept("child2", "child2.1");
+ add.accept("child2.1", className);
+ add.accept(superclass, "child2.1");
+ add.accept(superclass, "superclassDependency");
+
+ expectedAllNonPageDependencies.remove(className);
+
+ final Set<String> allNonPageDependencies =
componentDependencyRegistry.getAllNonPageDependencies(className);
+ assertFalse(allNonPageDependencies.contains(className));
+ assertEquals(expectedAllNonPageDependencies, allNonPageDependencies);
+
+ }
+
@Test
public void register()
{
@@ -390,8 +454,8 @@ public class ComponentDependencyRegistryImplTest
assertDependencies(OuterAny.class, Any.class);
// @Component, type() defined
-
componentDependencyRegistry.register(EmbeddedComponentTypeConflict.class);
- assertDependencies(EmbeddedComponentTypeConflict.class,
TextField.class);
+ componentDependencyRegistry.register(AtComponentType.class);
+ assertDependencies(AtComponentType.class, TextField.class,
Border.class);
// @Mixin, type() not defined
componentDependencyRegistry.register(AbstractTextField.class);
@@ -416,8 +480,15 @@ public class ComponentDependencyRegistryImplTest
// Templates with <t:replace>
componentDependencyRegistry.register(SubclassWithImport.class);
assertDependencies(SubclassWithImport.class,
- OutputRaw.class, SuperclassWithImport.class);
-
+ OutputRaw.class, SuperclassWithImport.class, Loop.class);
+
+ // Circular dependency: BlockHolder <-> BlockCaller
+ componentDependencyRegistry.register(BlockHolder.class);
+ assertDependencies(BlockHolder.class,
+ BlockCaller.class, Loop.class, ActionLink.class);
+ assertDependencies(BlockCaller.class,
+ BlockHolder.class, Border.class, PageLink.class,
Delegate.class);
+
}
private void assertDependencies(Class clasz, Class... dependencies) {
@@ -433,14 +504,14 @@ public class ComponentDependencyRegistryImplTest
.collect(Collectors.toSet());
}
-// private static Set<String> setOf(String ... strings)
-// {
-// return new HashSet<>(Arrays.asList(strings));
-// }
-
private void add(String component, String dependency)
{
- componentDependencyRegistry.add(component, dependency,
DependencyType.USAGE, true);
+ add(component, dependency, DependencyType.USAGE);
+ }
+
+ private void add(String component, String dependency, DependencyType type)
+ {
+ componentDependencyRegistry.add(component, dependency, type, true);
}
private static final class MockMappedConfiguration<String, URL> implements
MappedConfiguration<String, URL>
@@ -474,4 +545,46 @@ public class ComponentDependencyRegistryImplTest
}
+ final private static class FileResource extends AbstractResource
+ {
+
+ final private File file;
+
+ final private String path;
+
+ public FileResource(String path, File file)
+ {
+ super(path);
+ this.file = file;
+ this.path = path;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public java.net.URL toURL()
+ {
+ try
+ {
+ final File actualFile = new File(file, path);
+ return actualFile.exists() ? actualFile.toURL() : null;
+ } catch (MalformedURLException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected Resource newResource(String path)
+ {
+ return new FileResource(path, file);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "FileResource [file=" + file + "/" + path + "]";
+ }
+
+ }
+
}
diff --git a/tapestry-core/src/test/resources/log4j.properties
b/tapestry-core/src/test/resources/log4j.properties
index febecf129..9dcdaacb2 100644
--- a/tapestry-core/src/test/resources/log4j.properties
+++ b/tapestry-core/src/test/resources/log4j.properties
@@ -21,6 +21,7 @@
log4j.category.org.apache.tapestry5.integration.app1.base.InheritBase=debug
log4j.category.org.apache.tapestry5.integration.app1.pages.inherit=debug
#log4j.category.org.apache.tapestry5.services.pageload=debug
+log4j.category.org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub=off
log4j.category.org.apache.tapestry5.integration.app1.services.AppModule.TimingFilter=ERROR
#
log4j.category.tapestry.transformer.org.apache.tapestry5.integration.app1.base.InheritBase=debug
#
log4j.category.tapestry.transformer.org.apache.tapestry5.integration.app1.pages.inherit=debug