This is an automated email from the ASF dual-hosted git repository. thiagohp pushed a commit to branch better-page-invalidation in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit ca9e4c75d6440ff798b60a8cdf3f4804482767d5 Author: Thiago H. de Paula Figueiredo <[email protected]> AuthorDate: Thu Apr 27 23:37:23 2023 -0300 TAP-2742: live class reloading works, production mode doesn'r --- 583_RELEASE_NOTES.md | 1 + .../services/PropertyConduitSourceImpl.java | 1 - .../internal/plastic/PlasticClassLoader.java | 83 ++++++++- .../internal/plastic/PlasticClassPool.java | 63 +++++-- .../apache/tapestry5/plastic/PlasticManager.java | 9 + .../org/apache/tapestry5/plastic/PlasticUtils.java | 10 + .../internal/plastic/PlasticUtilsTests.groovy | 17 ++ .../tapestry5/corelib/pages/PageCatalog.java | 191 +------------------ .../tapestry5/internal/bindings/PropBinding.java | 9 - .../internal/bindings/PropBindingFactory.java | 4 +- .../internal/renderers/RequestRenderer.java | 2 + .../services/ComponentDependencyRegistryImpl.java | 96 +++++++++- .../services/ComponentInstantiatorSource.java | 8 - .../services/ComponentInstantiatorSourceImpl.java | 78 +++++--- .../internal/services/PageSourceImpl.java | 57 ++++-- .../internal/services/RequestErrorFilter.java | 77 ++++---- .../internal/services/RequestPageCacheImpl.java | 103 +++-------- .../internal/structure/ComponentPageElement.java | 1 + .../internal/transform/ComponentWorker.java | 7 +- .../tapestry5/internal/transform/ImportWorker.java | 27 +-- .../internal/transform/InjectComponentWorker.java | 16 +- .../internal/transform/InjectPageWorker.java | 13 +- .../apache/tapestry5/modules/TapestryModule.java | 18 +- .../services/pageload/PageClassloaderContext.java | 81 +++++++- .../pageload/PageClassloaderContextManager.java | 11 +- .../PageClassloaderContextManagerImpl.java | 204 ++++++++++++++++----- .../integration/app1/services/AppModule.java | 8 +- .../ComponentDependencyRegistryImplTest.java | 97 +++++++++- tapestry-core/src/test/resources/log4j.properties | 4 +- 29 files changed, 798 insertions(+), 498 deletions(-) diff --git a/583_RELEASE_NOTES.md b/583_RELEASE_NOTES.md index 7db54d7a8..882312c15 100644 --- a/583_RELEASE_NOTES.md +++ b/583_RELEASE_NOTES.md @@ -7,6 +7,7 @@ Scratch pad for changes destined for the 5.8.3 release notes page. * getValues() to MultiKey * New getLogicalName() method in ComponentClassResolver. * New getPlasticManager() method in PlasticProxyFactory +* New getPageNames() method in BeanBlockOverrideSource # Non-backward-compatible changes (but that probably won't cause problems) diff --git a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java index 4b474491d..66f106660 100644 --- a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java +++ b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java @@ -1390,7 +1390,6 @@ public class PropertyConduitSourceImpl implements PropertyConduitSource @PostInjection public void listenForInvalidations(@ComponentClasses InvalidationEventHub hub) { - System.out.println("WWWWWW hub " + hub); hub.addInvalidationCallback(this::listen); } 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 a4ba43cc1..fdd7e9599 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 @@ -14,6 +14,11 @@ package org.apache.tapestry5.internal.plastic; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apache.tapestry5.plastic.PlasticUtils; + public class PlasticClassLoader extends ClassLoader { static @@ -23,11 +28,16 @@ public class PlasticClassLoader extends ClassLoader } private final ClassLoaderDelegate delegate; - - public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate delegate) + + private Predicate<String> filter; + + private Function<String, Class<?>> alternativeClassloading; + + private String tag; + + public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate delegate) { super(parent); - this.delegate = delegate; } @@ -41,9 +51,27 @@ public class PlasticClassLoader extends ClassLoader if (loadedClass != null) return loadedClass; - if (delegate.shouldInterceptClassLoading(name)) + if (shouldInterceptClassLoading(name)) { - Class<?> c = delegate.loadAndTransformClass(name); + Class<?> c = null; + if (filter == null || filter.test(name)) + { + c = delegate.loadAndTransformClass(name); + } + else if (alternativeClassloading != null) + { + c = alternativeClassloading.apply(name); + } + + if (c == null) + { + return super.loadClass(name, resolve); + } + + if (name.endsWith(".BeanEditor$Prepare")) + { + System.out.printf("LLLL Loading %s in classloader '%s' : %s\n", name, tag, this); + } if (resolve) resolveClass(c); @@ -56,6 +84,11 @@ public class PlasticClassLoader extends ClassLoader } } + private boolean shouldInterceptClassLoading(String name) { + return delegate.shouldInterceptClassLoading( + PlasticUtils.getEnclosingClassName(name)); + } + public synchronized Class<?> defineClassWithBytecode(String className, byte[] bytecode) { synchronized(getClassLoadingLock(className)) @@ -63,4 +96,44 @@ public class PlasticClassLoader extends ClassLoader return defineClass(className, bytecode, 0, bytecode.length); } } + + /** + * When alternatingClassloader is set, this classloader delegates to it the + * call to {@linkplain ClassLoader#loadClass(String)}. If it returns a non-null object, + * it's returned by <code>loadClass(String)</code>. Otherwise, it returns + * <code>super.loadClass(name)</code>. + * @since 5.8.3 + */ + public void setAlternativeClassloading(Function<String, Class<?>> alternateClassloading) + { + this.alternativeClassloading = alternateClassloading; + } + + /** + * @since 5.8.3 + */ + public void setTag(String tag) + { + this.tag = tag; + } + + /** + * When a filter is set, only classes accepted by it will be loaded by this classloader. + * Instead, it will be delegated to alternate classloading first and the parent classloader + * in case the alternate doesn't handle it. + * @since 5.8.3 + */ + public void setFilter(Predicate<String> filter) + { + this.filter = filter; + } + + @Override + public String toString() + { + final String superToString = super.toString(); + final String id = superToString.substring(superToString.indexOf('@')).trim(); + return String.format("PlasticClassLoader[%s, tag=%s, parent=%s]", id, tag, getParent()); + } + } 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 8ee547f23..1a099843c 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 @@ -14,22 +14,44 @@ package org.apache.tapestry5.internal.plastic; -import org.apache.tapestry5.internal.plastic.asm.ClassReader; -import org.apache.tapestry5.internal.plastic.asm.ClassWriter; -import org.apache.tapestry5.internal.plastic.asm.Opcodes; -import org.apache.tapestry5.internal.plastic.asm.tree.*; -import org.apache.tapestry5.plastic.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.Stack; import java.util.concurrent.CopyOnWriteArrayList; +import org.apache.tapestry5.internal.plastic.asm.ClassReader; +import org.apache.tapestry5.internal.plastic.asm.ClassWriter; +import org.apache.tapestry5.internal.plastic.asm.Opcodes; +import org.apache.tapestry5.internal.plastic.asm.tree.AbstractInsnNode; +import org.apache.tapestry5.internal.plastic.asm.tree.AnnotationNode; +import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode; +import org.apache.tapestry5.internal.plastic.asm.tree.FieldInsnNode; +import org.apache.tapestry5.internal.plastic.asm.tree.InsnList; +import org.apache.tapestry5.internal.plastic.asm.tree.MethodInsnNode; +import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode; +import org.apache.tapestry5.plastic.AnnotationAccess; +import org.apache.tapestry5.plastic.ClassInstantiator; +import org.apache.tapestry5.plastic.ClassType; +import org.apache.tapestry5.plastic.PlasticClassEvent; +import org.apache.tapestry5.plastic.PlasticClassListener; +import org.apache.tapestry5.plastic.PlasticClassListenerHub; +import org.apache.tapestry5.plastic.PlasticClassTransformation; +import org.apache.tapestry5.plastic.PlasticConstants; +import org.apache.tapestry5.plastic.PlasticManagerDelegate; +import org.apache.tapestry5.plastic.TransformationOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Responsible for managing a class loader that allows ASM {@link ClassNode}s * to be instantiated as runtime classes. @@ -63,6 +85,8 @@ public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticCl private final StaticContext emptyStaticContext = new StaticContext(); private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>(); + + private PlasticClassPool parent; private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>() { @@ -482,9 +506,17 @@ public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticCl if (shouldInterceptClassLoading(baseClassName)) { loader.loadClass(baseClassName); + + PlasticClassPool current = this; - BaseClassDef def = baseClassDefs.get(baseClassName); - + BaseClassDef def = current.baseClassDefs.get(baseClassName); + + while (def == null && current.parent != null) + { + current = current.parent; + def = current.baseClassDefs.get(baseClassName); + } + assert def != null; return new PlasticClassImpl(classNode, implementationClassNode, this, def.inheritanceData, def.staticContext, proxy); @@ -669,6 +701,15 @@ public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticCl listeners.remove(listener); } + + /** + * Sets the parent of this instance. Only used to look up baseClassDefs. + * @since 5.8.3 + */ + public void setParent(PlasticClassPool parent) + { + this.parent = parent; + } boolean isEnabled(TransformationOption option) { diff --git a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java index 1d42bd6b7..49249db25 100644 --- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java +++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java @@ -356,4 +356,13 @@ public class PlasticManager implements PlasticClassListenerHub return pool.shouldInterceptClassLoading(className); } + /** + * Returns the Plastic class pool used by this instance. + * @since 5.8.3 + */ + public PlasticClassPool getPool() + { + return pool; + } + } diff --git a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java index 6ed424e30..2fab09efd 100644 --- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java +++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java @@ -141,4 +141,14 @@ public class PlasticUtils { return PrimitiveType.getByName(typeName) != null; } + + /** + * If the given class is an inner class, returns the enclosing class. + * Otherwise, returns the class name unchanged. + */ + public static String getEnclosingClassName(String className) + { + int index = className.indexOf('$'); + return index <= 0 ? className : className.substring(0, index); + } } diff --git a/plastic/src/test/groovy/org/apache/tapestry5/internal/plastic/PlasticUtilsTests.groovy b/plastic/src/test/groovy/org/apache/tapestry5/internal/plastic/PlasticUtilsTests.groovy index 8a2f4ea51..801f74cf2 100644 --- a/plastic/src/test/groovy/org/apache/tapestry5/internal/plastic/PlasticUtilsTests.groovy +++ b/plastic/src/test/groovy/org/apache/tapestry5/internal/plastic/PlasticUtilsTests.groovy @@ -155,4 +155,21 @@ class PlasticUtilsTests extends Specification "int" | true "java.lang.Integer" | false } + + def "getEnclosingClass #name should be #expected"() + { + + expect: + + PlasticUtils.getEnclosingClassName(name) == expected + + where: + + name | expected + + "org.apache.tapestry5.corelib.components.PropertyEditor" | "org.apache.tapestry5.corelib.components.PropertyEditor" + "org.apache.tapestry5.corelib.components.PropertyEditor\$CleanupEnvironment" | "org.apache.tapestry5.corelib.components.PropertyEditor" + + } + } 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 e887c0da1..abaedb62d 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 @@ -14,53 +14,31 @@ package org.apache.tapestry5.corelib.pages; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.alerts.AlertManager; -import org.apache.tapestry5.annotations.InjectComponent; -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.annotations.*; import org.apache.tapestry5.beaneditor.Validate; import org.apache.tapestry5.beanmodel.BeanModel; import org.apache.tapestry5.beanmodel.services.BeanModelSource; import org.apache.tapestry5.commons.Messages; -import org.apache.tapestry5.commons.services.InvalidationEventHub; import org.apache.tapestry5.commons.util.CollectionFactory; import org.apache.tapestry5.corelib.components.Zone; -import org.apache.tapestry5.dom.Element; -import org.apache.tapestry5.func.F; -import org.apache.tapestry5.func.Flow; -import org.apache.tapestry5.func.Mapper; -import org.apache.tapestry5.func.Predicate; -import org.apache.tapestry5.func.Reducer; +import org.apache.tapestry5.func.*; import org.apache.tapestry5.http.TapestryHttpSymbolConstants; -import org.apache.tapestry5.http.services.Request; import org.apache.tapestry5.internal.PageCatalogTotals; -import org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator; -import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; import org.apache.tapestry5.internal.services.PageSource; import org.apache.tapestry5.internal.services.ReloadHelper; -import org.apache.tapestry5.internal.structure.ComponentPageElement; import org.apache.tapestry5.internal.structure.Page; import org.apache.tapestry5.ioc.OperationTracker; -import org.apache.tapestry5.ioc.annotations.ComponentClasses; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.internal.util.InternalUtils; -import org.apache.tapestry5.runtime.Component; import org.apache.tapestry5.services.ComponentClassResolver; -import org.apache.tapestry5.services.ajax.AjaxResponseRenderer; -import org.apache.tapestry5.services.javascript.JavaScriptSupport; import org.apache.tapestry5.services.pageload.ComponentResourceSelector; +import java.util.Collection; +import java.util.List; +import java.util.Set; + /** * Lists out the currently loaded pages, using a {@link org.apache.tapestry5.corelib.components.Grid}. * Provides an option to force all pages to be loaded. In development mode, includes an option to clear the page cache. @@ -86,9 +64,6 @@ public class PageCatalog @Inject private ComponentClassResolver resolver; - - @Inject - private ComponentDependencyRegistry componentDependencyRegistry; @Inject private AlertManager alertManager; @@ -96,17 +71,8 @@ public class PageCatalog @Property private Page page; - @Property - private Page selectedPage; - - @Property - private String dependency; - @InjectComponent private Zone pagesZone; - - @InjectComponent - private Zone pageStructureZone; @Persist private Set<String> failures; @@ -124,32 +90,13 @@ public class PageCatalog @Inject private BeanModelSource beanModelSource; - + @Inject private Messages messages; @Property public static BeanModel<Page> model; - @Inject - private Request request; - - @Inject - @ComponentClasses - private InvalidationEventHub classesInvalidationEventHub; - - @Inject - private JavaScriptSupport javaScriptSupport; - - @Inject - private ComponentDependencyGraphvizGenerator componentDependencyGraphvizGenerator; - - @Inject - private ComponentClassResolver componentClassResolver; - - @Inject - private AjaxResponseRenderer ajaxResponseRenderer; - void pageLoaded() { model = beanModelSource.createDisplayModel(Page.class, messages); @@ -158,7 +105,6 @@ public class PageCatalog model.addExpression("assemblyTime", "stats.assemblyTime"); model.addExpression("componentCount", "stats.componentCount"); model.addExpression("weight", "stats.weight"); - model.add("clear", null); model.reorder("name", "selector", "assemblyTime", "componentCount", "weight"); } @@ -207,16 +153,7 @@ public class PageCatalog { return pageSource.getAllPages(); } - - Object onClearPage(String className) - { - final String logicalName = resolver.getLogicalName(className); - classesInvalidationEventHub.fireInvalidationEvent(Arrays.asList(className)); - alertManager.warn(String.format("Page %s (%s) has been cleared from the page cache", - className, logicalName)); - return pagesZone.getBody(); - } - + Object onSuccessFromSinglePageLoad() { boolean found = !F.flow(getPages()).filter(new Predicate<Page>() @@ -333,19 +270,6 @@ public class PageCatalog return pagesZone.getBody(); } - - Object onActionFromStoreDependencyInformation() - { - - componentDependencyRegistry.writeFile(); - - alertManager.warn(String.format( - "Component dependency information written to %s.", - ComponentDependencyRegistry.FILENAME)); - - return pagesZone.getBody(); - - } Object onActionFromRunGC() { @@ -367,103 +291,4 @@ public class PageCatalog { return String.format("%,.3f ms", millis); } - - public List<String> getDependencies() - { - List<String> dependencies = new ArrayList<>(componentDependencyRegistry.getDependencies(getSelectedPageClassName())); - Collections.sort(dependencies); - return dependencies; - } - - public void onPageStructure(String pageName) - { - selectedPage = pageSource.getPage(pageName); - ajaxResponseRenderer.addRender("pageStructureZone", pageStructureZone.getBody()); - } - - public String getDisplayLogicalName() - { - return getDisplayLogicalName(dependency); - } - - public String getPageClassName() - { - return getClassName(page); - } - - public String getSelectedPageClassName() - { - return getClassName(selectedPage); - } - - private String getClassName(Page page) - { - return page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName(); - } - - private String getClassName(Component component) - { - return component.getComponentResources().getComponentModel().getComponentClassName(); - } - - public void onComponentTree(MarkupWriter writer) - { - render(selectedPage.getRootElement(), writer); - } - - private void render(ComponentPageElement componentPageElement, MarkupWriter writer) - { - final Element li = writer.element("li"); - final String className = getClassName(componentPageElement.getComponent()); - final Set<String> embeddedElementIds = componentPageElement.getEmbeddedElementIds(); - - if (componentPageElement.getComponent().getComponentResources().getComponentModel().isPage()) - { - li.text(componentPageElement.getPageName()); - } - else { - li.text(String.format("%s (%s)", getDisplayLogicalName(className), componentPageElement.getId())); - } - - if (!embeddedElementIds.isEmpty()) - { - writer.element("ul"); - for (String id : embeddedElementIds) - { - render(componentPageElement.getEmbeddedElement(id), writer); - } - writer.end(); - } - - writer.end(); - } - - private String getDisplayLogicalName(final String className) - { - final String logicalName = resolver.getLogicalName(className); - String displayName = logicalName; - if (logicalName == null || logicalName.trim().length() == 0) - { - if (className.contains(".base.")) - { - displayName = "(base class)"; - } - if (className.contains(".mixins.")) - { - displayName = "(mixin)"; - } - } - return displayName; - } - - public String getLogicalName(String className) - { - return resolver.getLogicalName(className); - } - - public String getGraphvizValue() - { - return componentDependencyGraphvizGenerator.generate(getClassName(selectedPage)); - } - } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBinding.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBinding.java index 973c42b41..8a7778153 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBinding.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBinding.java @@ -16,7 +16,6 @@ package org.apache.tapestry5.internal.bindings; import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import java.util.concurrent.atomic.AtomicInteger; import org.apache.tapestry5.beanmodel.PropertyConduit; import org.apache.tapestry5.beanmodel.PropertyConduit2; @@ -40,10 +39,6 @@ public class PropBinding extends AbstractBinding implements InternalPropBinding private boolean invariant; private final String expression; - - // TODO remove - private static final AtomicInteger COUNTER = new AtomicInteger(); - private final int id; public PropBinding(final Location location, final Object root, final PropertyConduit conduit, final String expression, final String toString) { @@ -55,10 +50,6 @@ public class PropBinding extends AbstractBinding implements InternalPropBinding this.toString = toString; invariant = conduit.getAnnotation(Invariant.class) != null; - - System.out.println("TTTTT new PropBinding: " + root.getClass().getSimpleName() + ":" + expression); - id = COUNTER.getAndIncrement(); - } /** diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBindingFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBindingFactory.java index c8e51ffcb..e2bcd29e7 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBindingFactory.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/bindings/PropBindingFactory.java @@ -68,10 +68,10 @@ public class PropBindingFactory implements BindingFactory try { final PageClassloaderContext context = pageClassloaderContextManager.get(className); - System.out.printf("XXXXX Target class (before): %s classloader : %s\n", targetClass.getSimpleName(), targetClass.getClassLoader()); +// System.out.printf("XXXXX Target class (before): %s classloader : %s\n", targetClass.getSimpleName(), targetClass.getClassLoader()); targetClass = context.getProxyFactory() .getClassLoader().loadClass(className); - System.out.printf("XXXXX Target class (after) : %s classloader : %s context %s\n", targetClass.getSimpleName(), targetClass.getClassLoader(), context.getName()); +// System.out.printf("XXXXX Target class (after) : %s classloader : %s context %s\n", targetClass.getSimpleName(), targetClass.getClassLoader(), context.getName()); } catch (ClassNotFoundException e) { throw new TapestryException(e.getMessage(), e); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/renderers/RequestRenderer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/renderers/RequestRenderer.java index 326ca7226..c0b0e956f 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/renderers/RequestRenderer.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/renderers/RequestRenderer.java @@ -77,6 +77,8 @@ public class RequestRenderer implements ObjectRenderer<Request> headers(request, writer); attributes(request, writer); context(writer); + + // TODO: remove methods below pageClassloaderContext(writer); pages(writer); } 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 a694cdb71..9e5198cef 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 @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -76,6 +77,10 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis // Key is a component, values are the components that depend on it. final private Map<String, Set<String>> map; + // Dependencies that are only for invalidation (i.e. from one component to + // a page injected through @InjectPage + final private Set<InvalidationOnlyDependency> invalidationOnlyDependencies; + // Cache to check which classes were already processed or not. final private Set<String> alreadyProcessed; @@ -104,6 +109,7 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis this.pageClassloaderContextManager = pageClassloaderContextManager; map = new HashMap<>(); alreadyProcessed = new HashSet<>(); + invalidationOnlyDependencies = new HashSet<InvalidationOnlyDependency>(); this.plasticManager = plasticManager; this.resolver = componentClassResolver; this.templateParser = templateParser; @@ -150,6 +156,7 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis public void register(Class<?> component) { + final String className = component.getName(); final Set<Class<?>> furtherDependencies = new HashSet<>(); Consumer<Class<?>> processClass = furtherDependencies::add; Consumer<String> processClassName = s -> { @@ -168,15 +175,22 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis for (Field field : component.getDeclaredFields()) { - // Component and page injection annotation - if (field.isAnnotationPresent(InjectPage.class) || - field.isAnnotationPresent(InjectComponent.class)) + // Component injection annotation + if (field.isAnnotationPresent(InjectComponent.class)) { final Class<?> dependency = field.getType(); add(component, dependency); processClass.accept(dependency); } + // Page injection annotation + if (field.isAnnotationPresent(InjectComponent.class)) + { + final Class<?> dependency = field.getType(); + invalidationOnlyDependencies.add( + new InvalidationOnlyDependency(className, dependency.getName())); + } + // @Component registerComponentInstance(field, processClassName); @@ -195,14 +209,16 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis add(component, superclass); } - alreadyProcessed.add(component.getName()); + Set<String> dynamicDependencies; + + alreadyProcessed.add(className); for (Class<?> dependency : furtherDependencies) { // Avoid infinite recursion final String dependencyClassName = dependency.getName(); if (!alreadyProcessed.contains(dependencyClassName) - && !plasticManager.shouldInterceptClassLoading(dependency.getName())) + && plasticManager.shouldInterceptClassLoading(dependency.getName())) { register(dependency); } @@ -443,6 +459,18 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis } } } + synchronized (invalidationOnlyDependencies) + { + final Iterator<InvalidationOnlyDependency> iterator = invalidationOnlyDependencies.iterator(); + while (iterator.hasNext()) + { + InvalidationOnlyDependency dependency = iterator.next(); + if (dependency.className.equals(className) || dependency.dependency.equals(className)) + { + iterator.remove(); + } + } + } } @Override @@ -467,10 +495,16 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis @Override public Set<String> getDependencies(String className) { - return map.entrySet().stream() + Set<String> dependencies = Collections.emptySet(); + if (alreadyProcessed.contains(className)) + { + dependencies = map.entrySet().stream() .filter(e -> e.getValue().contains(className)) .map(e -> e.getKey()) .collect(Collectors.toSet()); + } + + return dependencies; } private void add(ComponentPageElement component, ComponentPageElement dependency) @@ -491,7 +525,7 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis } } - private void add(Class component, Class dependency) + private void add(Class<?> component, Class<?> dependency) { if (plasticManager.shouldInterceptClassLoading(dependency.getName())) { @@ -536,11 +570,13 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis } // Don't invalidate component dependency information when // PageClassloaderContextManager is merging contexts + // TODO is this still needed since the inception of INVALIDATIONS_ENABLED? else if (!pageClassloaderContextManager.isMerging()) { furtherDependents = new ArrayList<>(); for (String resource : resources) { + final Set<String> dependents = getDependents(resource); for (String furtherDependent : dependents) { @@ -549,6 +585,15 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis furtherDependents.add(furtherDependent); } } + + for (InvalidationOnlyDependency invalidationOnlyDependency : invalidationOnlyDependencies) + { + if (invalidationOnlyDependency.dependency.equals(resource)) + { + furtherDependents.add(invalidationOnlyDependency.dependency); + } + } + clear(resource); } } @@ -783,5 +828,42 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis } + final private class InvalidationOnlyDependency + { + + private String className; + private String dependency; + + public InvalidationOnlyDependency(String className, String dependency) + { + super(); + this.className = className; + this.dependency = dependency; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Objects.hash(className, dependency); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof InvalidationOnlyDependency)) { + return false; + } + InvalidationOnlyDependency other = (InvalidationOnlyDependency) obj; + return Objects.equals(className, other.className) && Objects.equals(dependency, other.dependency); + } + + } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java index 1291d7444..ed7a40f34 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java @@ -12,8 +12,6 @@ package org.apache.tapestry5.internal.services; -import java.util.List; - import org.apache.tapestry5.commons.services.PlasticProxyFactory; import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; import org.apache.tapestry5.services.transform.ControlledPackageType; @@ -70,10 +68,4 @@ public interface ComponentInstantiatorSource * @since 5.3 */ void forceComponentInvalidation(); - - /** - * Invalidates the caches for a given list of class names. - * @since 5.8.3 - */ - void invalidate(List<String> classNames); } 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 588c99dde..63edfcb0b 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 @@ -117,6 +117,10 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia private PageClassloaderContext rootPageClassloaderContext; private PlasticProxyFactoryProxy plasticProxyFactoryProxy; + + private ComponentDependencyRegistry componentDependencyRegistry; + + private static final ThreadLocal<String> CURRENT_PAGE = ThreadLocal.withInitial(() -> null); /** * Map from class name to Instantiator. @@ -161,7 +165,9 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia InternalComponentInvalidationEventHub invalidationHub, - PageClassloaderContextManager pageClassloaderContextManager + PageClassloaderContextManager pageClassloaderContextManager, + + ComponentDependencyRegistry componentDependencyRegistry ) { this.parent = proxyFactory.getClassLoader(); @@ -174,6 +180,7 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia this.productionMode = productionMode; this.resolver = resolver; this.pageClassloaderContextManager = pageClassloaderContextManager; + this.componentDependencyRegistry = componentDependencyRegistry; // For now, we just need the keys of the configuration. When there are more types of controlled // packages, we'll need to do more. @@ -191,7 +198,7 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia @PostInjection public void listenForUpdates(UpdateListenerHub hub) { -// invalidationHub.addInvalidationCallback(this); + invalidationHub.addInvalidationCallback(this::invalidate); hub.addUpdateListener(this); } @@ -224,27 +231,32 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia } } - @Override - public void invalidate(final List<String> classNames) { + private List<String> invalidate(final List<String> classNames) { + + final String currentPage = CURRENT_PAGE.get(); final Iterator<Entry<String, Instantiator>> classToInstantiatorIterator = classToInstantiator.entrySet().iterator(); while (classToInstantiatorIterator.hasNext()) { - if (classNames.contains(classToInstantiatorIterator.next().getKey())) + final String className = classToInstantiatorIterator.next().getKey(); + if (!className.equals(currentPage) && classNames.contains(className)) { +// System.out.println("WWWWW Removing class instantiator " + className); classToInstantiatorIterator.remove(); } } - // TODO: fix this -// final Iterator<Entry<String, ComponentModel>> classToModelIterator = classToModel.entrySet().iterator(); -// while (classToModelIterator.hasNext()) -// { -// if (classNames.contains(classToModelIterator.next().getKey())) -// { -// classToModelIterator.remove(); -// } -// } + final Iterator<Entry<String, ComponentModel>> classToModelIterator = classToModel.entrySet().iterator(); + while (classToModelIterator.hasNext()) + { + final String className = classToModelIterator.next().getKey(); + if (!className.equals(currentPage) && classNames.contains(className)) + { +// System.out.println("WWWWW Removing class model " + className); + classToModelIterator.remove(); + } + } + return Collections.emptyList(); } public void forceComponentInvalidation() @@ -252,14 +264,14 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia changeTracker.clear(); invalidationHub.classInControlledPackageHasChanged(); pageClassloaderContextManager.clear(); + classToModel.clear(); } public void run() { changeTracker.clear(); classToInstantiator.clear(); - // TODO fix this -// classToModel.clear(); + classToModel.clear(); pageClassloaderContextManager.clear(); initializeService(); } @@ -312,6 +324,9 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia { return classToInstantiator.computeIfAbsent(className, this::createInstantiatorForClass); } + + private static final ThreadLocal<Set<String>> OPEN_INSTANTIATORS = + ThreadLocal.withInitial(HashSet::new); private Instantiator createInstantiatorForClass(final String className) { @@ -320,14 +335,33 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia { public Instantiator invoke() { + // Force the creation of the class (and the transformation of the class). This will first // trigger transformations of any base classes. - - final PageClassloaderContext context = pageClassloaderContextManager.get(className); - final ClassInstantiator<Component> plasticInstantiator = - context.getPlasticManager().getClassInstantiator(className); - + + OPEN_INSTANTIATORS.get().add(className); + + componentDependencyRegistry.disableInvalidations(); + PageClassloaderContext context = pageClassloaderContextManager.get(className); + componentDependencyRegistry.enableInvalidations(); + + // Make sure the dependencies have been processed in case + // there was some invalidation going on and they're not there. + + final Set<String> dependencies = componentDependencyRegistry.getDependencies(className); + for (String dependency : dependencies) + { + if (!OPEN_INSTANTIATORS.get().contains(dependency)) + { +// System.out.println("TTTTT calling createInstaniatoForClass " + dependency); + createInstantiatorForClass(dependency); + } + } + + ClassInstantiator<Component> plasticInstantiator = context.getPlasticManager().getClassInstantiator(className); final ComponentModel model = classToModel.get(className); + + OPEN_INSTANTIATORS.get().remove(className); return new Instantiator() { @@ -345,7 +379,7 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia @Override public String toString() { - return String.format("[Instantiator[%s]", className); + return String.format("[Instantiator[%s:%s]", className, context); } }; } 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 79b2a51dd..b442646c6 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 @@ -139,27 +139,44 @@ public class PageSourceImpl implements PageSource // different threads. The last built one will "evict" the others from the page cache, // and the earlier ones will be GCed. -// if (!productionMode) -// { -// // Make sure you get a fresh version of the class before processing its -// // dependencies -// final String className = componentClassResolver.resolvePageNameToClassName(canonicalPageName); -// pageClassloaderContextManager.clear(className); -// final PageClassloaderContext context = pageClassloaderContextManager.get(className); -// final Class<?> clasz; -// try { -// clasz = context.getProxyFactory().getClassLoader().loadClass(className); -// pageClassloaderContextManager.invalidateAndFireInvalidationEvents(context); -// componentDependencyRegistry.register(clasz); -// pageClassloaderContextManager.get(className); -// } catch (ClassNotFoundException e) -// { -// logger.error(e.getMessage(), e); -// } -// -// } + // TODO: maybe remove this + if (!productionMode) + { + + final String className = componentClassResolver.resolvePageNameToClassName(canonicalPageName); + if (!componentDependencyRegistry.contains(canonicalPageName)) + { + // Make sure you get a fresh version of the class before processing its + // dependencies + pageClassloaderContextManager.clear(className); + final PageClassloaderContext context = pageClassloaderContextManager.get(className); + final Class<?> clasz; + try { + + clasz = context.getProxyFactory().getClassLoader().loadClass(className); + pageClassloaderContextManager.invalidateAndFireInvalidationEvents(context); + componentDependencyRegistry.register(clasz); + + // The call to register() above will potentially cause some dependencies + // to wrongly end in the the page's context's classloader instead of one of its parent + // context/classloader. That's why we need to invalidate the page's current + // context and classloader and create a new instance of each. +// componentDependencyRegistry.disableInvalidations(); +// final PageClassloaderContext toInvalidate = +// pageClassloaderContextManager.get(className); +// pageClassloaderContextManager.invalidate(toInvalidate); +// componentDependencyRegistry.disableInvalidations(); + pageClassloaderContextManager.get(className); + } catch (ClassNotFoundException e) + { + throw new RuntimeException(e); + } + + } + + } - page = pageLoader.loadPage(canonicalPageName, selector); + page = pageLoader.loadPage(canonicalPageName, selector); ref = new SoftReference<Page>(page); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestErrorFilter.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestErrorFilter.java index 32fa090ef..f1fdc6c87 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestErrorFilter.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestErrorFilter.java @@ -65,44 +65,44 @@ public class RequestErrorFilter implements RequestFilter catch (Throwable ex) { -// if (request.getParameter(QUERY_PARAMETER) == null) -// { -// -// Throwable rootCause = ex.getCause(); -// String classToInvalidate = getClassToInvalidate(rootCause); -// -// if (classToInvalidate != null) -// { -// -// final List<String> classesToInvalidate = -// Arrays.asList(classToInvalidate, ExceptionReport.class.getName()); -// componentInstantiatorSource.invalidate(classesToInvalidate); -// classesInvalidationHub.fireInvalidationEvent(classesToInvalidate); -// -// Link link = null; -// -// final ComponentEventRequestParameters componentEventParameters = componentEventLinkEncoder.decodeComponentEventRequest(request); -// if (componentEventParameters != null) -// { -// link = componentEventLinkEncoder.createComponentEventLink(componentEventParameters, false); -// } -// -// final PageRenderRequestParameters pageRenderParameters = componentEventLinkEncoder.decodePageRenderRequest(request); -// if (pageRenderParameters != null) -// { -// link = componentEventLinkEncoder.createPageRenderLink(pageRenderParameters); -// } -// -// if (link != null) -// { -// link.addParameter(QUERY_PARAMETER, "true"); -// response.sendRedirect(link); -// return true; -// } -// -// } -// -// } + // TODO: evaluate removing this + if (request.getParameter(QUERY_PARAMETER) == null) + { + + Throwable rootCause = ex.getCause(); + String classToInvalidate = getClassToInvalidate(rootCause); + + if (classToInvalidate != null) + { + + final List<String> classesToInvalidate = + Arrays.asList(classToInvalidate, ExceptionReport.class.getName()); + classesInvalidationHub.fireInvalidationEvent(classesToInvalidate); + + Link link = null; + + final ComponentEventRequestParameters componentEventParameters = componentEventLinkEncoder.decodeComponentEventRequest(request); + if (componentEventParameters != null) + { + link = componentEventLinkEncoder.createComponentEventLink(componentEventParameters, false); + } + + final PageRenderRequestParameters pageRenderParameters = componentEventLinkEncoder.decodePageRenderRequest(request); + if (pageRenderParameters != null) + { + link = componentEventLinkEncoder.createPageRenderLink(pageRenderParameters); + } + + if (link != null) + { + link.addParameter(QUERY_PARAMETER, "true"); + response.sendRedirect(link); + return true; + } + + } + + } // Most of the time, we've got exception linked up the kazoo ... but when ClassLoaders // get involved, things go screwy. Exceptions when transforming classes can cause @@ -110,6 +110,7 @@ public class RequestErrorFilter implements RequestFilter // TAPESTRY-2078 System.out.println("YYYYYY" + pageClassloaderContextManager.getRoot().toRecursiveString()); + ex.printStackTrace(); Throwable exceptionToReport = attachNewCause(ex, internalRequestGlobals.getClassLoaderException()); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestPageCacheImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestPageCacheImpl.java index a0c06d4f5..99563acb0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestPageCacheImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestPageCacheImpl.java @@ -14,24 +14,22 @@ package org.apache.tapestry5.internal.services; +import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Set; -import org.apache.tapestry5.SymbolConstants; +import org.apache.tapestry5.commons.services.InvalidationEventHub; import org.apache.tapestry5.commons.util.CollectionFactory; import org.apache.tapestry5.commons.util.ExceptionUtils; -import org.apache.tapestry5.corelib.pages.ExceptionReport; import org.apache.tapestry5.http.services.RequestGlobals; import org.apache.tapestry5.internal.InternalConstants; import org.apache.tapestry5.internal.structure.Page; import org.apache.tapestry5.ioc.ScopeConstants; +import org.apache.tapestry5.ioc.annotations.ComponentClasses; import org.apache.tapestry5.ioc.annotations.PostInjection; import org.apache.tapestry5.ioc.annotations.Scope; -import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.services.PerthreadManager; import org.apache.tapestry5.services.ComponentClassResolver; -import org.apache.tapestry5.services.pageload.PageClassloaderContext; -import org.apache.tapestry5.services.pageload.PageClassloaderContextManager; import org.slf4j.Logger; /** @@ -55,30 +53,21 @@ public class RequestPageCacheImpl implements RequestPageCache, Runnable private final Map<String, Page> cache = CollectionFactory.newMap(); - private final PageClassloaderContextManager pageClassloaderContextManager; - - private final ComponentDependencyRegistry componentDependencyRegistry; - - private final boolean productionMode; - - public RequestPageCacheImpl(Logger logger, ComponentClassResolver resolver, PageSource pageSource, RequestGlobals requestGlobals, - PageClassloaderContextManager pageClassloaderContextManager, - ComponentDependencyRegistry componentDependencyRegistry, - @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode) + public RequestPageCacheImpl(Logger logger, ComponentClassResolver resolver, + PageSource pageSource, RequestGlobals requestGlobals) { this.logger = logger; this.resolver = resolver; this.pageSource = pageSource; this.requestGlobals = requestGlobals; - this.pageClassloaderContextManager = pageClassloaderContextManager; - this.productionMode = productionMode; - this.componentDependencyRegistry = componentDependencyRegistry; } @PostInjection - public void listenForThreadCleanup(PerthreadManager perthreadManager) + public void listenForThreadCleanup(PerthreadManager perthreadManager, + @ComponentClasses InvalidationEventHub classesHub) { perthreadManager.addThreadCleanupCallback(this); + classesHub.addInvalidationCallback(this::listen); } public void run() @@ -103,69 +92,13 @@ public class RequestPageCacheImpl implements RequestPageCache, Runnable if (page == null) { -// if (!productionMode) -// { -// final String className = resolver.getClassName(canonical); -// if (!componentDependencyRegistry.contains(className)) -// { -// final PageClassloaderContext context = pageClassloaderContextManager.get(className); -// -// try { -// Class<?> clasz = context.getProxyFactory().getClassLoader().loadClass(className); -// componentDependencyRegistry.register(clasz); -// if (context.isUnknown()) -// { -// pageClassloaderContextManager.invalidateAndFireInvalidationEvents(context); -// } -// componentDependencyRegistry.register(clasz); -// if (context.isUnknown()) -// { -// pageClassloaderContextManager.get(className); -// } -// } catch (ClassNotFoundException e) { -// logger.warn("Exception while loading class " + className, e); -// } -// } -// } - page = pageSource.getPage(canonical); - -// // In production mode, we don't have the unknown context nor -// // page invalidation. -// if (!productionMode) -// { -// -// // If the page is in the unknown page classloader context, it means we don't -// // know its dependencies yet. Since we just got an instance assembled, -// // let's throw it away and rebuild with known depedencies. This way, -// // we avoid ClassCastExceptions later on classes used by it. -// final String className = page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName(); -// -// final PageClassloaderContext context = pageClassloaderContextManager.get(className); -// if (context.isUnknown() && false) -// { -// System.out.println("XXXXX Before unknown invalidation"); -// System.out.println(root.toRecursiveString()); -// componentDependencyRegistry.disableInvalidations(); -// pageClassloaderContextManager.invalidateAndFireInvalidationEvents(context); -// componentDependencyRegistry.enableInvalidations(); -// // Force processing the classloader context for this page so it's ready -// // when the new page instance is created -// pageClassloaderContextManager.get(className); -// page = pageSource.getPage(canonical); -// } -// -// } try { page.attached(); - } - catch (Throwable t) + } catch (Throwable t) { - System.out.println("XXXX " + pageClassloaderContextManager.getRoot()); - cache.remove(canonical); - pageClassloaderContextManager.get(ExceptionReport.class.getName()); throw new RuntimeException(String.format("Unable to attach page %s: %s", canonical, ExceptionUtils.toMessage(t)), t); } @@ -181,4 +114,20 @@ public class RequestPageCacheImpl implements RequestPageCache, Runnable return page; } + + private List<String> listen(List<String> resources) + { + // TODO: we probably don't need this anymore + for (String resource : resources) + { + if (resolver.isPage(resource)) + { + final String canonicalName = resolver.canonicalizePageName( + resolver.getLogicalName(resource)); + cache.remove(canonicalName); + } + } + return Collections.emptyList(); + } + } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java index 1e17fe90b..2fdf0b1ff 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java @@ -106,6 +106,7 @@ public interface ComponentPageElement extends ComponentResourcesCommon, Internal */ ComponentPageElement getEmbeddedElement(String id); + // TODO: remove /** * Returns the ids of all embedded elements defined within the component. * @since 5.8.3 diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java index bd4628ed9..54a4a487f 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java @@ -25,7 +25,6 @@ import org.apache.tapestry5.commons.util.CollectionFactory; import org.apache.tapestry5.commons.util.CommonsUtils; import org.apache.tapestry5.internal.KeyValue; import org.apache.tapestry5.internal.TapestryInternalUtils; -import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; import org.apache.tapestry5.ioc.Orderable; import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.model.ComponentModel; @@ -47,13 +46,10 @@ import java.util.List; public class ComponentWorker implements ComponentClassTransformWorker2 { private final ComponentClassResolver resolver; - - private final ComponentDependencyRegistry componentDependencyRegistry; - public ComponentWorker(ComponentClassResolver resolver, ComponentDependencyRegistry componentDependencyRegistry) + public ComponentWorker(ComponentClassResolver resolver) { this.resolver = resolver; - this.componentDependencyRegistry = componentDependencyRegistry; } public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) @@ -61,7 +57,6 @@ public class ComponentWorker implements ComponentClassTransformWorker2 for (PlasticField field : plasticClass.getFieldsWithAnnotation(Component.class)) { transformField(plasticClass, model, field); - componentDependencyRegistry.register(field, model); } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java index c7038c2b7..a3801a6ea 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java @@ -14,13 +14,11 @@ package org.apache.tapestry5.internal.transform; import org.apache.tapestry5.Asset; import org.apache.tapestry5.ComponentResources; -import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.annotations.Import; import org.apache.tapestry5.annotations.SetupRender; import org.apache.tapestry5.func.F; import org.apache.tapestry5.func.Mapper; import org.apache.tapestry5.func.Worker; -import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker; import org.apache.tapestry5.ioc.services.SymbolSource; import org.apache.tapestry5.model.MutableComponentModel; import org.apache.tapestry5.plastic.*; @@ -46,10 +44,6 @@ public class ImportWorker implements ComponentClassTransformWorker2 private final SymbolSource symbolSource; private final AssetSource assetSource; - - private final ResourceChangeTracker resourceChangeTracker; - - private final boolean productionMode; private final Worker<Asset> importLibrary = new Worker<Asset>() { @@ -75,27 +69,21 @@ public class ImportWorker implements ComponentClassTransformWorker2 } }; - public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource, - ResourceChangeTracker resourceChangeTracker) + public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource) { this.javascriptSupport = javascriptSupport; this.symbolSource = symbolSource; this.assetSource = assetSource; - this.resourceChangeTracker = resourceChangeTracker; - this.productionMode = Boolean.valueOf(symbolSource.valueForSymbol(SymbolConstants.PRODUCTION_MODE)); } public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model) { - resourceChangeTracker.setCurrentClassName(model.getComponentClassName()); processClassAnnotationAtSetupRenderPhase(componentClass, model); for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class)) { decorateMethod(componentClass, model, m); } - - resourceChangeTracker.clearCurrentClassName(); } private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model) @@ -124,6 +112,8 @@ public class ImportWorker implements ComponentClassTransformWorker2 { importStacks(method, annotation.stack()); + String libraryName = model.getLibraryName(); + importLibraries(componentClass, model, method, annotation.library()); importStylesheets(componentClass, model, method, annotation.stylesheet()); @@ -274,7 +264,6 @@ public class ImportWorker implements ComponentClassTransformWorker2 private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access, final Worker<Asset> operation) { - final String className = method.getPlasticClass().getClassName(); method.addAdvice(new MethodAdvice() { public void advise(MethodInvocation invocation) @@ -283,17 +272,7 @@ public class ImportWorker implements ComponentClassTransformWorker2 Asset[] assets = (Asset[]) access.get(invocation.getInstance()); - if (!productionMode) - { - resourceChangeTracker.setCurrentClassName(className); - } - F.flow(assets).each(operation); - - if (!productionMode) - { - resourceChangeTracker.clearCurrentClassName(); - } } }); } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java index 2240a418b..b18518ca9 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java @@ -19,14 +19,9 @@ import org.apache.tapestry5.annotations.InjectComponent; import org.apache.tapestry5.commons.util.DifferentClassVersionsException; import org.apache.tapestry5.commons.util.UnknownValueException; import org.apache.tapestry5.internal.services.ComponentClassCache; -import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.model.MutableComponentModel; -import org.apache.tapestry5.plastic.ComputedValue; -import org.apache.tapestry5.plastic.FieldConduit; -import org.apache.tapestry5.plastic.InstanceContext; -import org.apache.tapestry5.plastic.PlasticClass; -import org.apache.tapestry5.plastic.PlasticField; +import org.apache.tapestry5.plastic.*; import org.apache.tapestry5.runtime.Component; import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; import org.apache.tapestry5.services.transform.TransformationSupport; @@ -124,13 +119,9 @@ public class InjectComponentWorker implements ComponentClassTransformWorker2 private final Logger logger; - private final ComponentDependencyRegistry componentDependencyRegistry; - - public InjectComponentWorker(ComponentClassCache classCache, - ComponentDependencyRegistry componentDependencyRegistry, final Logger logger) + public InjectComponentWorker(ComponentClassCache classCache, final Logger logger) { this.classCache = classCache; - this.componentDependencyRegistry = componentDependencyRegistry; this.logger = logger; } @@ -161,9 +152,6 @@ public class InjectComponentWorker implements ComponentClassTransformWorker2 }; field.setComputedConduit(provider); - - componentDependencyRegistry.register(field, model); - } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectPageWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectPageWorker.java index 6b9870663..dc36b58f4 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectPageWorker.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectPageWorker.java @@ -16,7 +16,6 @@ package org.apache.tapestry5.internal.transform; import org.apache.tapestry5.annotations.InjectPage; import org.apache.tapestry5.commons.ObjectCreator; -import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.ioc.services.PerthreadManager; import org.apache.tapestry5.model.MutableComponentModel; @@ -66,26 +65,22 @@ public class InjectPageWorker implements ComponentClassTransformWorker2 private final PerthreadManager perThreadManager; - private final ComponentDependencyRegistry componentDependencyRegistry; - - public InjectPageWorker(ComponentSource componentSource, ComponentClassResolver resolver, PerthreadManager perThreadManager, - ComponentDependencyRegistry componentDependencyRegistry) + public InjectPageWorker(ComponentSource componentSource, ComponentClassResolver resolver, PerthreadManager perThreadManager) { this.componentSource = componentSource; this.resolver = resolver; this.perThreadManager = perThreadManager; - this.componentDependencyRegistry = componentDependencyRegistry; } public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) { for (PlasticField field : plasticClass.getFieldsWithAnnotation(InjectPage.class)) { - addInjectedPage(field, model); + addInjectedPage(field); } } - private void addInjectedPage(PlasticField field, MutableComponentModel model) + private void addInjectedPage(PlasticField field) { InjectPage annotation = field.getAnnotation(InjectPage.class); @@ -99,7 +94,5 @@ public class InjectPageWorker implements ComponentClassTransformWorker2 .resolvePageClassNameToPageName(field.getTypeName()) : pageName; field.setConduit(new InjectedPageConduit(field.getPlasticClass().getClassName(), fieldName, injectedPageName)); - - componentDependencyRegistry.register(field, model); } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java index 9c4a55827..ba60eb924 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java @@ -20,15 +20,19 @@ import java.math.BigInteger; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.tapestry5.Asset; import org.apache.tapestry5.BindingConstants; @@ -104,7 +108,12 @@ import org.apache.tapestry5.commons.services.TypeCoercer; import org.apache.tapestry5.commons.util.AvailableValues; import org.apache.tapestry5.commons.util.CollectionFactory; import org.apache.tapestry5.commons.util.StrategyRegistry; +import org.apache.tapestry5.corelib.components.BeanEditor; +import org.apache.tapestry5.corelib.components.PropertyDisplay; +import org.apache.tapestry5.corelib.components.PropertyEditor; import org.apache.tapestry5.corelib.data.SecureOption; +import org.apache.tapestry5.corelib.pages.PropertyDisplayBlocks; +import org.apache.tapestry5.corelib.pages.PropertyEditBlocks; import org.apache.tapestry5.grid.GridConstants; import org.apache.tapestry5.grid.GridDataSource; import org.apache.tapestry5.http.Link; @@ -364,6 +373,7 @@ import org.apache.tapestry5.services.meta.FixedExtractor; import org.apache.tapestry5.services.meta.MetaDataExtractor; import org.apache.tapestry5.services.meta.MetaWorker; import org.apache.tapestry5.services.pageload.PageClassloaderContextManager; +import org.apache.tapestry5.services.pageload.PageClassloaderContextManagerImpl; import org.apache.tapestry5.services.pageload.PreloaderMode; import org.apache.tapestry5.services.rest.MappedEntityManager; import org.apache.tapestry5.services.rest.OpenApiDescriptionGenerator; @@ -687,7 +697,7 @@ public final class TapestryModule configuration.addInstance("ApplicationState", ApplicationStateWorker.class); configuration.addInstance("Environment", EnvironmentalWorker.class); - configuration.add("Component", new ComponentWorker(resolver, componentDependencyRegistry)); + configuration.add("Component", new ComponentWorker(resolver)); configuration.add("Mixin", new MixinWorker(resolver)); configuration.addInstance("InjectPage", InjectPageWorker.class); configuration.addInstance("InjectComponent", InjectComponentWorker.class); @@ -2792,7 +2802,7 @@ public final class TapestryModule componentDependencyRegistry.listen(componentTemplateSource.getInvalidationEventHub()); return componentDependencyRegistry; } - + private static final class TapestryCoreComponentLibraryInfoSource implements ComponentLibraryInfoSource { @@ -2813,8 +2823,8 @@ public final class TapestryModule info.setDescription("Components provided out-of-the-box by Tapestry"); info.setDocumentationUrl("http://tapestry.apache.org/component-reference.html"); info.setJavadocUrl("http://tapestry.apache.org/current/apidocs/"); - info.setSourceBrowseUrl("https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=summary"); - info.setSourceRootUrl("https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=blob;f=tapestry-core/src/main/java/"); + info.setSourceBrowseUrl("https://gitbox.apache.org/repos/asf?p=tapestry-5.git;a=summary"); + info.setSourceRootUrl("https://gitbox.apache.org/repos/asf?p=tapestry-5.git;a=blob;f=tapestry-core/src/main/java/"); info.setIssueTrackerUrl("https://issues.apache.org/jira/browse/TAP5"); info.setHomepageUrl("http://tapestry.apache.org"); info.setLibraryMapping(libraryMapping); 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 1b17f69a9..64bc37c30 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 @@ -20,7 +20,10 @@ import java.util.Objects; import java.util.Set; 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; import org.slf4j.LoggerFactory; @@ -45,13 +48,15 @@ public class PageClassloaderContext private final PlasticManager plasticManager; private final PlasticProxyFactory proxyFactory; + + private PageClassloaderContext root; /** * Name of the <code>unknown</code> context (i.e. the one for controlled classes * without dependency information at the moment). */ public static final String UNKOWN_CONTEXT_NAME = "unknown"; - + public PageClassloaderContext(String name, PageClassloaderContext parent, Set<String> classNames, @@ -64,6 +69,66 @@ public class PageClassloaderContext this.plasticManager = plasticProxyFactory.getPlasticManager(); this.proxyFactory = plasticProxyFactory; children = new HashSet<>(); + if (plasticProxyFactory.getClassLoader() instanceof PlasticClassLoader) + { + final PlasticClassLoader plasticClassLoader = (PlasticClassLoader) plasticManager.getClassLoader(); + plasticClassLoader.setTag(name); + plasticClassLoader.setFilter(this::filter); + plasticClassLoader.setAlternativeClassloading(this::alternativeClassLoading); + if (parent != null) + { + getPlasticManager().getPool().setParent(parent.getPlasticManager().getPool()); + } + } + } + + private Class<?> alternativeClassLoading(String className) + { + Class<?> clasz = null; + setRootFieldIfNeeded(); + PageClassloaderContext context = root.findByClassName( + PlasticUtils.getEnclosingClassName(className)); + if (isRoot() && context == null) + { + context = this; + } + if (context != null) + { + try + { + final PlasticClassLoader classLoader = (PlasticClassLoader) context.getClassLoader(); + // Avoiding infinite recursion + synchronized (classLoader) + { + classLoader.setAlternativeClassloading(null); + clasz = classLoader.loadClass(className); + classLoader.setAlternativeClassloading(this::alternativeClassLoading); + } + } catch (ClassNotFoundException e) + { + throw new RuntimeException(e); + } + } + return clasz; + } + + private void setRootFieldIfNeeded() + { + if (root == null) + { + if (isRoot()) + { + root = this; + } + else + { + root = this; + while (!root.isRoot()) + { + root = root.getParent(); + } + } + } } /** @@ -181,7 +246,7 @@ public class PageClassloaderContext } LOGGER.debug("Invalidating page classloader context '{}' (class loader {}, classes : {})", name, proxyFactory.getClassLoader(), classNames); - classNames.clear(); +// classNames.clear(); parent.getChildren().remove(this); proxyFactory.clearCache(); } @@ -282,4 +347,16 @@ public class PageClassloaderContext } return string; } + + private boolean filter(String className) + { + final int index = className.indexOf("$"); + if (index > 0) + { + className = className.substring(0, index); + } + // TODO: do we really need the className.contains(".base.") part? + return classNames.contains(className) || className.contains(".base.") || isUnknown(); + } + } 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 8ead155d3..3d3ff1b20 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 @@ -17,8 +17,8 @@ import java.util.Set; import java.util.function.Function; import org.apache.tapestry5.commons.services.PlasticProxyFactory; -import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; import org.apache.tapestry5.internal.services.ComponentInstantiatorSource; +import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration; /** * Service that creates {@linkplain PageClassloaderContext} instances (except the root one) @@ -26,6 +26,7 @@ import org.apache.tapestry5.internal.services.ComponentInstantiatorSource; * contexts may be reused for a given class, specially when in production mode. * * @see ComponentInstantiatorSource + * @see DynamicDependenciesDefinition * @since 5.8.3 */ public interface PageClassloaderContextManager @@ -79,4 +80,12 @@ public interface PageClassloaderContextManager */ void initialize(PageClassloaderContext root, Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider); + /** + * Returns the Class instance appropriate for a given component given a page name. + * @param clasz the class instance. + * @param pageName the page name. + * @return a Class instance. + */ + Class<?> getClassInstance(Class<?> clasz, String pageName); + } 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 94771139b..054129f44 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 @@ -25,12 +25,14 @@ import java.util.function.Supplier; import java.util.stream.Collectors; 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.services.ComponentDependencyRegistry; import org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub; import org.apache.tapestry5.ioc.annotations.ComponentClasses; import org.apache.tapestry5.ioc.annotations.Symbol; +import org.apache.tapestry5.plastic.PlasticUtils; import org.apache.tapestry5.services.ComponentClassResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,7 +113,7 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext else { - final String enclosingClassName = getEnclosingClassName(className); + final String enclosingClassName = PlasticUtils.getEnclosingClassName(className); context = root.findByClassName(enclosingClassName); if (context == null) @@ -132,7 +134,7 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext if (!className.equals(enclosingClassName)) { - context.addClass(className); + loadClass(className, context); } } @@ -143,18 +145,10 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext } - /** - * If the given class is an inner class, returns the enclosing class. - * Otherwise, returns the class name unchanged. - */ - private String getEnclosingClassName(String className) - { - int index = className.indexOf('$'); - return index <= 0 ? className : className.substring(0, index); - } - private PageClassloaderContext getUnknownContext(final PageClassloaderContext root, - final Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider) { + final Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider) + { + PageClassloaderContext unknownContext = null; for (PageClassloaderContext child : root.getChildren()) @@ -186,16 +180,29 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext return processUsingDependencies(className, root, unknownContextProvider, plasticProxyFactoryProvider, classesToInvalidate, new HashSet<>()); } - private PageClassloaderContext processUsingDependencies( String className, PageClassloaderContext root, Supplier<PageClassloaderContext> unknownContextProvider, - Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, Set<String> classesToInvalidate, + Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, + Set<String> classesToInvalidate, Set<String> alreadyProcessed) + { + return processUsingDependencies(className, root, unknownContextProvider, + plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed, true); + } + + + private PageClassloaderContext processUsingDependencies( + String className, + PageClassloaderContext root, + Supplier<PageClassloaderContext> unknownContextProvider, + Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, + Set<String> classesToInvalidate, + Set<String> alreadyProcessed, + boolean processCircularDependencies) { PageClassloaderContext context = root.findByClassName(className); - Set<String> circularDependencies = new HashSet<>(1); if (context == null) { @@ -209,18 +216,23 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext !componentDependencyRegistry.contains(className) && componentDependencyRegistry.getDependents(className).isEmpty()) { - // Make sure you get a fresh version of the class before processing its - // dependencies - Class<?> clasz; - PlasticProxyFactory throwaway = plasticProxyFactoryProvider.apply(root.getClassLoader()); - try { - clasz = throwaway.getClassLoader().loadClass(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - componentDependencyRegistry.register(clasz); - alreadyProcessed.remove(className); - return processUsingDependencies(className, root, unknownContextProvider, plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed); + context = unknownContextProvider.get(); +// // Make sure you get a fresh version of the class before processing its +// // dependencies +// PageClassloaderContext throwaway = new PageClassloaderContext( +// "Throwaway", +// root, +// Collections.singleton(className), +// plasticProxyFactoryProvider.apply(root.getClassLoader())); +// +// Class<?> clasz = loadClass(className, throwaway); +// componentDependencyRegistry.register(clasz); +// +// throwaway.getProxyFactory().clearCache(); +// throwaway.getClassNames().clear(); +// +// alreadyProcessed.remove(className); +// return processUsingDependencies(className, root, unknownContextProvider, plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed); } else { @@ -228,17 +240,18 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext alreadyProcessed.add(className); // Sorting dependencies alphabetically so we have consistent results. - List<String> dependencies = new ArrayList<>(componentDependencyRegistry.getDependencies(className)); + List<String> dependencies = new ArrayList<>(getDependenciesWithoutPages(className)); Collections.sort(dependencies); // Process dependencies depth-first for (String dependency : dependencies) { // Avoid infinite recursion loops - if (!alreadyProcessed.contains(dependency)) + if (!alreadyProcessed.contains(dependency)/* && + !circularDependencies.contains(dependency)*/) { processUsingDependencies(dependency, root, unknownContextProvider, - plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed); + plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed, false); } } @@ -246,13 +259,9 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext Set<PageClassloaderContext> contextDependencies = new HashSet<>(); for (String dependency : dependencies) { - // Direct circular dependency - if (componentDependencyRegistry.getDependencies(dependency).contains(className)) - { - circularDependencies.add(dependency); - } - else - { + // Circular dependency +// if (!circularDependencies.contains(dependency)) +// { PageClassloaderContext dependencyContext = root.findByClassName(dependency); if (dependencyContext == null) { @@ -261,7 +270,7 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext } contextDependencies.add(dependencyContext); - } +// } } if (contextDependencies.size() == 0) @@ -287,10 +296,19 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext getContextName(className), parentContext, Collections.singleton(className), - plasticProxyFactoryProvider.apply(root.getClassLoader())); + plasticProxyFactoryProvider.apply(parentContext.getClassLoader())); } 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 happened. + if (!componentClassResolver.isPage(className)/* + && circularDependencies.isEmpty()*/) + { + loadClass(className, context); + } LOGGER.debug("New context: {}", context); @@ -299,11 +317,64 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext } context.addClass(className); - for (String circularDependency : circularDependencies) + + // Merge contexts of circular dependencies into a single one + +// if (processCircularDependencies && !circularDependencies.isEmpty()) +// { +// Set<PageClassloaderContext> contexts = new HashSet<>(circularDependencies.size() + 1); +// contexts.add(context); +// for (String circularDependency : circularDependencies) +// { +// PageClassloaderContext toMerge = +// processUsingDependencies( +// circularDependency, root, unknownContextProvider, +// plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed, +// false); +// contexts.add(toMerge); +// } +// if (contexts.size() > 1) +// { +// +// context = merge(contexts, plasticProxyFactoryProvider, root, classesToInvalidate); +// +// // Avoiding an infinite recursion +// classesToInvalidate.removeAll(context.getClassNames()); +// +// // Ensure non-page class is initialized in the correct context and classloader +// for (String clasz : context.getClassNames()) { +// if (!componentClassResolver.isPage(clasz)) +// { +// loadClass(clasz, context); +// } +// } +// } +// } + return context; + } + + private Set<String> getDependenciesWithoutPages(String className) { + final Set<String> dependencies = componentDependencyRegistry.getDependencies(className); + return dependencies.stream() + .filter(c -> !componentClassResolver.isPage(c)) + .collect(Collectors.toSet()); + } + + private Class<?> loadClass(String className, PageClassloaderContext context) + { + try { - context.addClass(circularDependency); + final ClassLoader classLoader = context.getPlasticManager().getClassLoader(); + final Class<?> clasz = classLoader.loadClass(className); + // TODO: do were really need this loop? + for (Class<?> c : clasz.getDeclaredClasses()) + { + final Class<?> cccc = classLoader.loadClass(c.getName()); + } + return clasz; + } catch (Exception e) { + throw new RuntimeException(e); } - return context; } private PageClassloaderContext merge( @@ -403,6 +474,11 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext parent.addChild(merged); +// for (String className : classNames) +// { +// loadClass(className, merged); +// } + NESTED_MERGE_COUNT.set(NESTED_MERGE_COUNT.get() - 1); if (LOGGER.isDebugEnabled()) { @@ -488,6 +564,7 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext LOGGER.debug("Invalidating classes {}", classesToInvalidate); INVALIDATING_CONTEXT.set(true); final ArrayList<String> classesToInvalidateAsList = new ArrayList<>(classesToInvalidate); + // TODO: do we really need both invalidation hubs to be invoked here? invalidationHub.fireInvalidationEvent(classesToInvalidateAsList); componentClassesInvalidationEventHub.fireInvalidationEvent(classesToInvalidateAsList); INVALIDATING_CONTEXT.set(false); @@ -517,4 +594,45 @@ public class PageClassloaderContextManagerImpl implements PageClassloaderContext { } + @Override + public Class<?> getClassInstance(Class<?> clasz, String pageName) + { + final String className = clasz.getName(); + PageClassloaderContext context = root.findByClassName(className); + if (context == null) + { + context = get(className); + } +// final String pageClassName = componentClassResolver.resolvePageNameToClassName(pageName); +// final PageClassloaderContext context = get(pageClassName); +// PageClassloaderContext ancestor = context.getParent(); +// +// if (!className.equals(pageClassName)) +// { +// +// while (ancestor != null && !ancestor.getClassNames().contains(className)) +// { +// ancestor = ancestor.getParent(); +// } +// +// if (ancestor == null) +// { +// ancestor = context; +// } +// +// if (clasz.getSimpleName().equals("TextField")) +// { +// System.out.printf("XXXXX Target class (before): %s page %s context : %s\n", clasz.getSimpleName(), pageName, clasz.getClassLoader()); +// } + try + { + clasz = context.getProxyFactory().getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) + { + throw new TapestryException(e.getMessage(), e); + } +// } + return clasz; + } + } diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java index c9b5b7505..5f57836bf 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java @@ -139,7 +139,7 @@ public class AppModule { long elapsed = System.nanoTime() - startTime; -// log.info(String.format("Request time: %5.2f s -- %s", elapsed * 10E-10d, request.getPath())); + log.info(String.format("Request time: %5.2f s -- %s", elapsed * 10E-10d, request.getPath())); } } }; @@ -395,9 +395,9 @@ public class AppModule @Contribute(PagePreloader.class) public static void setupPagePreload(Configuration<String> configuration) { -// configuration.add("index"); -// configuration.add("core/exceptionreport"); -// configuration.add("core/t5dashboard"); + configuration.add("index"); + configuration.add("core/exceptionreport"); + configuration.add("core/t5dashboard"); } 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 847494f96..fcb776d6a 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 @@ -33,15 +33,35 @@ import org.apache.tapestry5.commons.MappedConfiguration; import org.apache.tapestry5.corelib.base.AbstractComponentEventLink; import org.apache.tapestry5.corelib.base.AbstractField; import org.apache.tapestry5.corelib.base.AbstractLink; +import org.apache.tapestry5.corelib.base.AbstractPropertyOutput; import org.apache.tapestry5.corelib.base.AbstractTextField; import org.apache.tapestry5.corelib.components.ActionLink; +import org.apache.tapestry5.corelib.components.Alerts; import org.apache.tapestry5.corelib.components.Any; import org.apache.tapestry5.corelib.components.BeanEditForm; +import org.apache.tapestry5.corelib.components.BeanEditor; +import org.apache.tapestry5.corelib.components.Delegate; +import org.apache.tapestry5.corelib.components.DevTool; +import org.apache.tapestry5.corelib.components.Errors; import org.apache.tapestry5.corelib.components.EventLink; +import org.apache.tapestry5.corelib.components.Form; +import org.apache.tapestry5.corelib.components.Glyphicon; import org.apache.tapestry5.corelib.components.If; +import org.apache.tapestry5.corelib.components.Label; +import org.apache.tapestry5.corelib.components.Loop; +import org.apache.tapestry5.corelib.components.Output; +import org.apache.tapestry5.corelib.components.PageLink; +import org.apache.tapestry5.corelib.components.PropertyDisplay; +import org.apache.tapestry5.corelib.components.PropertyEditor; +import org.apache.tapestry5.corelib.components.RenderObject; +import org.apache.tapestry5.corelib.components.Submit; import org.apache.tapestry5.corelib.components.TextField; +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.PropertyDisplayBlocks; +import org.apache.tapestry5.corelib.pages.PropertyEditBlocks; import org.apache.tapestry5.integration.app1.components.Border; import org.apache.tapestry5.integration.app1.components.ErrorComponent; import org.apache.tapestry5.integration.app1.components.OuterAny; @@ -51,6 +71,7 @@ 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.BeanEditorWithFormFragmentDemo; import org.apache.tapestry5.integration.app1.pages.BlockCaller; import org.apache.tapestry5.integration.app1.pages.BlockHolder; import org.apache.tapestry5.integration.app1.pages.EmbeddedComponentTypeConflict; @@ -109,6 +130,22 @@ public class ComponentDependencyRegistryImplTest expectResolveComponent(If.class); expectResolveComponent(ErrorComponent.class); expectResolveComponent(EventLink.class); + expectResolveComponent(Output.class); + expectResolveComponent(Delegate.class); + expectResolveComponent(TextOutput.class); + expectResolveComponent(Label.class); + expectResolveComponent(BeanEditor.class); + expectResolveComponent(Loop.class); + expectResolveComponent(PropertyEditor.class); + expectResolveComponent(PropertyDisplay.class); + expectResolveComponent(Errors.class); + expectResolveComponent(Submit.class); + expectResolveComponent(PageLink.class); + expectResolveComponent(DevTool.class); + expectResolveComponent(Alerts.class); + expectResolveComponent(RenderObject.class); + expectResolveComponent(Form.class); + expectResolveComponent(Glyphicon.class); EasyMock.expect(resolver.resolveMixinTypeToClassName("textonlyondisabled")) .andReturn(TextOnlyOnDisabled.class.getName()).anyTimes(); @@ -116,6 +153,15 @@ public class ComponentDependencyRegistryImplTest .andReturn(EchoValue2.class.getName()).anyTimes(); EasyMock.expect(resolver.resolveMixinTypeToClassName("alttitledefault")) .andReturn(AltTitleDefault.class.getName()).anyTimes(); + 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."); + }).anyTimes(); pageClassloaderContextManager = EasyMock.createMock(PageClassloaderContextManager.class); plasticManager = EasyMock.createMock(PlasticManager.class); @@ -125,16 +171,23 @@ public class ComponentDependencyRegistryImplTest return className.contains(".pages.") || className.contains(".mixins.") || className.contains(".components.") || className.contains(".base."); }).anyTimes(); + componentDependencyRegistry = new ComponentDependencyRegistryImpl( - pageClassloaderContextManager, plasticManager, resolver, templateParser, componentTemplateLocator); + pageClassloaderContextManager, plasticManager, resolver, templateParser, + componentTemplateLocator); EasyMock.replay(pageClassloaderContextManager, plasticManager, resolver); } private void expectResolveComponent(final Class<?> clasz) { - EasyMock.expect(resolver.resolveComponentTypeToClassName(clasz.getSimpleName())) - .andReturn(clasz.getName()).anyTimes(); - EasyMock.expect(resolver.resolveComponentTypeToClassName(clasz.getSimpleName().toLowerCase())) - .andReturn(clasz.getName()).anyTimes(); + final String className = clasz.getName(); + final java.lang.String simpleName = clasz.getSimpleName(); + EasyMock.expect(resolver.resolveComponentTypeToClassName(simpleName)) + .andReturn(className).anyTimes(); + EasyMock.expect(resolver.resolveComponentTypeToClassName(simpleName.toLowerCase())) + .andReturn(className).anyTimes(); + EasyMock.expect(resolver.resolveComponentTypeToClassName( + simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1))) + .andReturn(className).anyTimes(); } private void configurePCCM(boolean merging) @@ -240,6 +293,14 @@ public class ComponentDependencyRegistryImplTest componentDependencyRegistry.clear(); + // Dynamic dependency definitions + componentDependencyRegistry.register(PropertyDisplay.class); + assertDependencies(PropertyDisplay.class, + PropertyDisplayBlocks.class, AbstractPropertyOutput.class); + + componentDependencyRegistry.register(PropertyEditor.class); + assertDependencies(PropertyEditor.class, PropertyEditBlocks.class); + // Superclass componentDependencyRegistry.register(EventLink.class); @@ -290,6 +351,32 @@ public class ComponentDependencyRegistryImplTest } + + @Test + public void getCircularDependencies() + { +// componentDependencyRegistry.clear(); +// componentDependencyRegistry.register(BeanEditor.class); +// Set<String> expected = setOf(PropertyEditor.class, PropertyEditBlocks.class); +// assertEquals(expected, +// componentDependencyRegistry.getCircularDependencies(BeanEditor.class.getName())); +// +// componentDependencyRegistry.register(BlockCaller.class); +// assertEquals(setOf(BlockHolder.class), +// componentDependencyRegistry.getCircularDependencies(BlockCaller.class.getName())); +// assertEquals(setOf(BlockCaller.class), +// componentDependencyRegistry.getCircularDependencies(BlockHolder.class.getName())); +// +// // An infinite recursion happened in this call. +// componentDependencyRegistry.register(Index.class); +// assertEquals(new HashSet<String>(), +// componentDependencyRegistry.getCircularDependencies(Delegate.class.getName())); + + componentDependencyRegistry.register(BeanEditorWithFormFragmentDemo.class); + + ComponentDependencyGraphvizGenerator generator = new ComponentDependencyGraphvizGeneratorImpl(componentDependencyRegistry, resolver); + System.out.println(generator.generate(BeanEditorWithFormFragmentDemo.class.getName())); + } private void assertDependencies(Class clasz, Class... dependencies) { assertEquals( diff --git a/tapestry-core/src/test/resources/log4j.properties b/tapestry-core/src/test/resources/log4j.properties index 6e07ce841..f9124f576 100644 --- a/tapestry-core/src/test/resources/log4j.properties +++ b/tapestry-core/src/test/resources/log4j.properties @@ -8,8 +8,6 @@ log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%t [%p] %c{1} %m%n log4j.category.org.apache.tapestry5.integration.app2=debug -log4j.category.org.apache.tapestry5.services.pageload.PageClassloaderContextManager=debug -log4j.category.org.apache.tapestry5.services.pageload.PageClassloaderContext=debug # log4j.category.tapestry.render=debug @@ -22,5 +20,7 @@ log4j.category.org.openqa.selenium.server=warn 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.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
