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 449b248c6b3e7ee179cbd08ba717424983ec26a9
Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br>
AuthorDate: Sun Mar 19 19:45:46 2023 -0300

    Partial work commit for better page invalidation part 2
---
 583_RELEASE_NOTES.md                               |   3 +
 .../services/PropertyConduitSourceImpl.java        |   1 +
 .../util/DifferentClassVersionsException.java      |  18 +-
 .../tapestry5/internal/bindings/PropBinding.java   |   9 +
 .../internal/bindings/PropBindingFactory.java      |  29 +-
 .../internal/renderers/RequestRenderer.java        | 120 +++++-
 .../services/ComponentClassResolverImpl.java       |  20 +-
 .../services/ComponentDependencyRegistry.java      |  21 +-
 .../services/ComponentDependencyRegistryImpl.java  | 449 ++++++++++++++++++++-
 .../services/ComponentInstantiatorSourceImpl.java  |  27 +-
 .../internal/services/PageSourceImpl.java          |  42 +-
 .../internal/services/RequestErrorFilter.java      |  85 ++--
 .../internal/services/RequestPageCacheImpl.java    |  86 +++-
 .../internal/transform/InjectComponentWorker.java  |   3 +-
 .../apache/tapestry5/modules/InternalModule.java   |   2 +-
 .../apache/tapestry5/modules/TapestryModule.java   |  22 +-
 .../tapestry5/services/ComponentClassResolver.java |   6 +
 .../services/pageload/PageClassloaderContext.java  | 106 ++++-
 .../pageload/PageClassloaderContextManager.java    |  37 +-
 .../PageClassloaderContextManagerImpl.java         | 415 +++++++++++++++++--
 .../apache/tapestry5/corelib/pages/PageCatalog.tml |   1 +
 .../integration/app1/components/Border.java        |   4 +
 .../app1/pages/InstanceMixinDependencies.java      |  30 ++
 .../integration/app1/services/AppModule.java       |   6 +-
 .../ComponentDependencyRegistryImplTest.java       | 260 +++++++++++-
 tapestry-core/src/test/resources/log4j.properties  |   2 +
 .../integration/app1/components/Border.tml         |   1 +
 27 files changed, 1651 insertions(+), 154 deletions(-)

diff --git a/583_RELEASE_NOTES.md b/583_RELEASE_NOTES.md
index e7923cc55..7db54d7a8 100644
--- a/583_RELEASE_NOTES.md
+++ b/583_RELEASE_NOTES.md
@@ -14,3 +14,6 @@ Scratch pad for changes destined for the 5.8.3 release notes 
page.
 * New getEmbeddedElementIds() method in ComponentPageElement (internal service)
 * New registerClassName() method in ResourceChangeTracker (internal service)
 * New clearClassName() method in ResourceChangeTracker (internal service)
+
+# Overall notes
+* Before 
\ No newline at end of file
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 66f106660..4b474491d 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,6 +1390,7 @@ 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/commons/src/main/java/org/apache/tapestry5/commons/util/DifferentClassVersionsException.java
 
b/commons/src/main/java/org/apache/tapestry5/commons/util/DifferentClassVersionsException.java
index 6bc062b18..ab490f71d 100644
--- 
a/commons/src/main/java/org/apache/tapestry5/commons/util/DifferentClassVersionsException.java
+++ 
b/commons/src/main/java/org/apache/tapestry5/commons/util/DifferentClassVersionsException.java
@@ -27,11 +27,17 @@ public class DifferentClassVersionsException extends 
TapestryException
     private static final long serialVersionUID = 1L;
     
     private final String className;
+    
+    private final ClassLoader classLoader1;
+    
+    private final ClassLoader classLoader2;
 
-    public DifferentClassVersionsException(String message, String className) 
+    public DifferentClassVersionsException(String message, String className, 
ClassLoader classLoader1, ClassLoader classLoader2) 
     {
         super(message, null);
         this.className = className;
+        this.classLoader1 = classLoader1;
+        this.classLoader2 = classLoader2;
     }
     
     public String getClassName() 
@@ -39,4 +45,14 @@ public class DifferentClassVersionsException extends 
TapestryException
         return className;
     }
     
+    public ClassLoader getClassLoader1() 
+    {
+        return classLoader1;
+    }
+    
+    public ClassLoader getClassLoader2() 
+    {
+        return classLoader2;
+    }
+    
 }
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 8a7778153..973c42b41 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,6 +16,7 @@ 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;
@@ -39,6 +40,10 @@ 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)
     {
@@ -50,6 +55,10 @@ 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 4fe2bf14a..c8e51ffcb 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
@@ -18,7 +18,10 @@ import org.apache.tapestry5.beanmodel.PropertyConduit;
 import org.apache.tapestry5.beanmodel.services.PropertyConduitSource;
 import org.apache.tapestry5.commons.Location;
 import org.apache.tapestry5.commons.internal.services.StringInterner;
+import org.apache.tapestry5.commons.internal.util.TapestryException;
 import org.apache.tapestry5.services.BindingFactory;
+import org.apache.tapestry5.services.pageload.PageClassloaderContext;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
 
 /**
  * Binding factory for reading and updating JavaBean properties.
@@ -31,18 +34,25 @@ public class PropBindingFactory implements BindingFactory
     private final PropertyConduitSource source;
 
     private final StringInterner interner;
+    
+    private final PageClassloaderContextManager pageClassloaderContextManager;
 
-    public PropBindingFactory(PropertyConduitSource propertyConduitSource, 
StringInterner interner)
+    public PropBindingFactory(PropertyConduitSource propertyConduitSource, 
StringInterner interner,
+            PageClassloaderContextManager pageClassloaderContextManager)
     {
         source = propertyConduitSource;
         this.interner = interner;
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
     }
 
     public Binding newBinding(String description, ComponentResources container,
                               ComponentResources component, String expression, 
Location location)
     {
+        
+        // TODO: need to get correct classloader here, probably
         Object target = container.getComponent();
         Class targetClass = target.getClass();
+        targetClass = getClassLoaderAppropriateClass(targetClass);
 
         PropertyConduit conduit = source.create(targetClass, expression);
 
@@ -51,4 +61,21 @@ public class PropBindingFactory implements BindingFactory
 
         return new PropBinding(location, target, conduit, expression, 
toString);
     }
+
+    private Class getClassLoaderAppropriateClass(Class targetClass)
+    {
+        final String className = targetClass.getName();
+        try 
+        {
+            final PageClassloaderContext context = 
pageClassloaderContextManager.get(className);
+            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());
+        } catch (ClassNotFoundException e) 
+        {
+            throw new TapestryException(e.getMessage(), e);
+        }
+        return targetClass;
+    }
 }
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 b5ebd0c2d..326ca7226 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
@@ -15,16 +15,26 @@
 package org.apache.tapestry5.internal.renderers;
 
 import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.util.CollectionFactory;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
 import org.apache.tapestry5.http.services.Context;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.PageSource;
+import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.ioc.annotations.Primary;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.ComponentClassResolver;
 import org.apache.tapestry5.services.ObjectRenderer;
+import org.apache.tapestry5.services.pageload.PageClassloaderContext;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class RequestRenderer implements ObjectRenderer<Request>
 {
@@ -33,12 +43,31 @@ public class RequestRenderer implements 
ObjectRenderer<Request>
     private final String contextPath;
 
     private final ObjectRenderer masterObjectRenderer;
-
-    public RequestRenderer(@Primary ObjectRenderer masterObjectRenderer, 
Context context, @Symbol(TapestryHttpSymbolConstants.CONTEXT_PATH) String 
contextPath)
+    
+    private final boolean productionMode;
+    
+    private final PageClassloaderContextManager pageClassloaderContextManager;
+    
+    private final PageSource pageSource;
+    
+    private final ComponentClassResolver componentClassResolver;
+
+    public RequestRenderer(
+            @Primary ObjectRenderer masterObjectRenderer, 
+            Context context, 
+            @Symbol(TapestryHttpSymbolConstants.CONTEXT_PATH) String 
contextPath,
+            @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+            PageClassloaderContextManager pageClassloaderContextManager,
+            PageSource pageSource,
+            ComponentClassResolver componentClassResolver)
     {
         this.masterObjectRenderer = masterObjectRenderer;
         this.context = context;
         this.contextPath = contextPath;
+        this.productionMode = productionMode;
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
+        this.pageSource = pageSource;
+        this.componentClassResolver = componentClassResolver;
     }
 
     public void render(Request request, MarkupWriter writer)
@@ -48,6 +77,8 @@ public class RequestRenderer implements 
ObjectRenderer<Request>
         headers(request, writer);
         attributes(request, writer);
         context(writer);
+        pageClassloaderContext(writer);
+        pages(writer);
     }
 
     private void coreProperties(Request request, MarkupWriter writer)
@@ -243,4 +274,89 @@ public class RequestRenderer implements 
ObjectRenderer<Request>
         writer.end(); // dl
     }
 
+    private void pageClassloaderContext(MarkupWriter writer) 
+    {
+        if (!productionMode)
+        {
+            section(writer, "Page Classloader Context");
+            writer.element("ul");
+            render(pageClassloaderContextManager.getRoot(), writer);
+            writer.end(); // ul
+        }
+    }
+
+    private void render(PageClassloaderContext context, MarkupWriter writer) 
+    {
+        if (context != null)
+        {
+        
+            writer.element("li");
+            
+            writer.element("p");
+            writer.element("em");
+            writer.write(context.getName());
+            writer.write(", ");
+            writer.write(context.getClassLoader().toString());
+            writer.end(); // em
+            writer.end(); // p
+            
+            writer.element("p");
+            
writer.write(context.getClassNames().stream().collect(Collectors.joining(", 
")));
+            writer.end(); // p
+            
+            if (!context.getChildren().isEmpty())
+            {
+                writer.element("ul");
+                for (PageClassloaderContext child : context.getChildren())
+                {
+                    render(child, writer);
+                }
+                writer.end(); // ul
+            }
+            writer.end(); // li
+            
+        }
+        
+    }
+    
+    private void pages(MarkupWriter writer) 
+    {
+        if (!productionMode)
+        {
+            section(writer, "Pages");
+            writer.element("table", "class", "table table-condensed 
table-hover table-striped exception-report-threads");
+            writer.element("thead");
+            
+            writer.element("td");
+            writer.write("Name");
+            writer.end(); //td Name
+
+            writer.element("td");
+            writer.write("Context");
+            writer.end(); //td Context
+
+            writer.end(); // thead
+            
+            writer.element("tbody");
+            
+            List<Page> pages = new ArrayList<>(pageSource.getAllPages());
+            Collections.sort(pages, Comparator.comparing(Page::getName));
+            
+            for (Page page : pages) {
+                writer.element("tr");
+                writer.element("td");
+                writer.write(page.getName());
+                writer.end(); // td                
+                writer.element("td");
+                
writer.write(pageClassloaderContextManager.getRoot().findByClassName(componentClassResolver.getClassName(page.getName())).toString());
+                writer.end(); // td                
+                writer.end(); // tr
+            }
+            
+            writer.end(); // tbody
+            
+            writer.end(); // table
+        }        
+    }
+    
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
index 349755fc7..77651e95c 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
@@ -826,13 +826,31 @@ public class ComponentClassResolverImpl implements 
ComponentClassResolver, Inval
         {
             result = getKeyByValue(thisData.componentToClassName, className);
         }
-        if (result == null ){
+        if (result == null )
+        {
             result = getKeyByValue(thisData.mixinToClassName, className);
         }
 
         return result;
     }
     
+    @Override
+    public String getClassName(String logicalName) 
+    {
+        final Data thisData = getData();
+        String result = getKeyByValue(thisData.pageClassNameToLogicalName, 
logicalName);
+        if (result == null)
+        {
+            result = thisData.componentToClassName.get(logicalName);
+        }
+        if (result == null )
+        {
+            result = thisData.mixinToClassName.get(logicalName);
+        }
+        return result;
+    }
+
+    
     private String getKeyByValue(Map<String, String> map, String value)
     {
         return map.entrySet().stream()
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
index c4ab81a6f..e2f10469a 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
@@ -39,7 +39,11 @@ public interface ComponentDependencyRegistry {
      */
     String FILENAME = "tapestryComponentDependencies.json";
 
-    
+    /**
+     * Register all the dependencies of a given class.
+     */
+    void register(Class<?> clasz);
+
     /**
      * Register all the dependencies of a given component.
      */
@@ -101,5 +105,20 @@ public interface ComponentDependencyRegistry {
      * Returns the set of all root classes (i.e. ones with no dependencies).
      */
     Set<String> getRootClasses();
+    
+    /**
+     * Returns whether stored dependency information is present.
+     */
+    boolean isStoredDependencyInformationPresent();
+    
+    /**
+     * Tells this service to ignore invalidations in this thread.
+     */
+    void disableInvalidations();
+    
+    /**
+     * Tells this service to stop ignoring invalidations in this thread.
+     */
+    void enableInvalidations();
 
 }
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 08689c4f3..a694cdb71 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
@@ -19,34 +19,56 @@ import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.annotations.InjectComponent;
 import org.apache.tapestry5.annotations.InjectPage;
+import org.apache.tapestry5.annotations.Mixin;
+import org.apache.tapestry5.annotations.MixinClasses;
+import org.apache.tapestry5.annotations.Mixins;
+import org.apache.tapestry5.commons.Resource;
 import org.apache.tapestry5.commons.internal.util.TapestryException;
 import org.apache.tapestry5.commons.services.InvalidationEventHub;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.parser.ComponentTemplate;
+import org.apache.tapestry5.internal.parser.StartComponentToken;
+import org.apache.tapestry5.internal.parser.TemplateToken;
 import org.apache.tapestry5.internal.structure.ComponentPageElement;
+import org.apache.tapestry5.ioc.Orderable;
+import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.model.EmbeddedComponentModel;
 import org.apache.tapestry5.model.MutableComponentModel;
+import org.apache.tapestry5.model.ParameterModel;
 import org.apache.tapestry5.plastic.PlasticField;
+import org.apache.tapestry5.plastic.PlasticManager;
 import org.apache.tapestry5.runtime.Component;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
+import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
+import org.slf4j.Logger;
 
 public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegistry 
 {
     
+    final private PageClassloaderContextManager pageClassloaderContextManager;
+    
     private static final String META_ATTRIBUTE = 
"injectedComponentDependencies";
     
     private static final String META_ATTRIBUTE_SEPARATOR = ",";
@@ -58,11 +80,34 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
     final private Set<String> alreadyProcessed;
     
     final private File storedDependencies;
-
-    public ComponentDependencyRegistryImpl()
+    
+    final private static ThreadLocal<Boolean> INVALIDATIONS_ENABLED = 
ThreadLocal.withInitial(() -> Boolean.TRUE);
+    
+    final private PlasticManager plasticManager;
+    
+    final private ComponentClassResolver resolver;
+    
+    final private TemplateParser templateParser;
+    
+    @SuppressWarnings("deprecation")
+    final private ComponentTemplateLocator componentTemplateLocator;
+    
+    final private boolean storedDependencyInformationPresent;
+    
+    public ComponentDependencyRegistryImpl(
+            final PageClassloaderContextManager pageClassloaderContextManager,
+            final PlasticManager plasticManager,
+            final ComponentClassResolver componentClassResolver,
+            final TemplateParser templateParser,
+            final ComponentTemplateLocator componentTemplateLocator)
     {
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
         map = new HashMap<>();
         alreadyProcessed = new HashSet<>();
+        this.plasticManager = plasticManager;
+        this.resolver = componentClassResolver;
+        this.templateParser = templateParser;
+        this.componentTemplateLocator = componentTemplateLocator;
         
         storedDependencies = new File(FILENAME);
         if (storedDependencies.exists())
@@ -84,7 +129,11 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
                             .stream()
                             .map(o -> (String) o)
                             .collect(Collectors.toSet());
-                    map.put(className, new HashSet<>(dependencies));
+                    for (String dependency : dependencies) 
+                    {
+                        add(className, dependency);
+                        alreadyProcessed.add(dependency);
+                    }
                     alreadyProcessed.add(className);
                 }
             } catch (IOException e) 
@@ -93,13 +142,200 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
             }
         }
         
+        storedDependencyInformationPresent = !map.isEmpty();
+        
+    }
+    
+    @Override
+    public void register(Class<?> component) 
+    {
+        
+        final Set<Class<?>> furtherDependencies = new HashSet<>();
+        Consumer<Class<?>> processClass = furtherDependencies::add;
+        Consumer<String> processClassName = s -> {
+            try {
+                
furtherDependencies.add(component.getClassLoader().loadClass(s));
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        };
+        
+        // Components declared in the template
+        registerTemplate(component, processClassName);
+        
+        // Dependencies from injecting or component-declaring annotations: 
+        // @InjectPage, @InjectComponent
+        for (Field field : component.getDeclaredFields())
+        {
+            
+            // Component and page injection annotation
+            if (field.isAnnotationPresent(InjectPage.class) || 
+                    field.isAnnotationPresent(InjectComponent.class))
+            {
+                final Class<?> dependency = field.getType();
+                add(component, dependency);
+                processClass.accept(dependency);
+            }
+            
+            // @Component
+            registerComponentInstance(field, processClassName);
+            
+            // Mixins, class level: @Mixin
+            registerMixin(field, processClassName);
+            
+            // Mixins applied to embedded component instances through 
@MixinClasses or @Mixins
+            registerComponentInstanceMixins(field, processClass, 
processClassName);
+        }
+
+        // Superclass
+        Class superclass = component.getSuperclass();
+        if (isTransformed(superclass))
+        {
+            processClass.accept(superclass);
+            add(component, superclass);
+        }
+        
+        alreadyProcessed.add(component.getName());
+        
+        for (Class<?> dependency : furtherDependencies) 
+        {
+            // Avoid infinite recursion
+            final String dependencyClassName = dependency.getName();
+            if (!alreadyProcessed.contains(dependencyClassName)
+                    && 
!plasticManager.shouldInterceptClassLoading(dependency.getName()))
+            {
+                register(dependency);
+            }
+        }
+        
+    }
+
+    /**
+     * Notice only the main template (i.e. not the locale- or axis-specific 
ones)
+     * are checked here. They hopefully will be covered when the 
ComponentModel-based
+     * component dependency processing is done.
+     * @param component
+     * @param processClassName 
+     */
+    @SuppressWarnings("deprecation")
+    private void registerTemplate(Class<?> component, Consumer<String> 
processClassName) 
+    {
+        // TODO: implement caching of template dependency information, probably
+        // by listening separaterly to ComponentTemplateSource to invalidate 
caches
+        // just when template changes.
+        
+        ComponentModel mock = new ComponentModelMock(component, 
resolver.isPage(component.getName()));
+        final Resource templateResource = 
componentTemplateLocator.locateTemplate(mock, Locale.getDefault());
+        String dependency;
+        if (templateResource != null)
+        {
+            final String className = component.getName();
+            final ComponentTemplate template = 
templateParser.parseTemplate(templateResource);
+            for (TemplateToken token:  template.getTokens())
+            {
+                if (token instanceof StartComponentToken) 
+                {
+                    StartComponentToken componentToken = (StartComponentToken) 
token;
+                    String logicalName = componentToken.getComponentType();
+                    if (logicalName != null)
+                    {
+                        dependency = 
resolver.resolveComponentTypeToClassName(logicalName);
+                        add(className, dependency);
+                        processClassName.accept(dependency);
+                    }
+                    for (String mixin : 
TapestryInternalUtils.splitAtCommas(componentToken.getMixins()))
+                    {
+                        dependency = 
resolver.resolveMixinTypeToClassName(mixin);
+                        add(className, dependency);
+                        processClassName.accept(dependency);
+                    }
+                }
+            }
+        }
+    }
+
+    private void registerComponentInstance(Field field, Consumer<String> 
processClassName)
+    {
+        if 
(field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class))
+        {
+            org.apache.tapestry5.annotations.Component component = 
+                    
field.getAnnotation(org.apache.tapestry5.annotations.Component.class);
+
+            final String typeFromAnnotation = component.type().trim();
+            String dependency;
+            if (typeFromAnnotation.isEmpty())
+            {
+                dependency = field.getType().getName();
+            }
+            else
+            {
+                dependency = 
resolver.resolveComponentTypeToClassName(typeFromAnnotation);
+            }
+            add(field.getDeclaringClass().getName(), dependency);
+            processClassName.accept(dependency);
+        }
+    }
+
+    private void registerMixin(Field field, Consumer<String> processClassName) 
{
+        if (field.isAnnotationPresent(Mixin.class))
+        {
+            // Logic adapted from MixinWorker
+            String mixinType = field.getAnnotation(Mixin.class).value();
+            String mixinClassName = InternalUtils.isBlank(mixinType) ? 
+                    getFieldTypeClassName(field) : 
+                    resolver.resolveMixinTypeToClassName(mixinType);
+            
+            add(getDeclaringClassName(field), mixinClassName);
+            processClassName.accept(mixinClassName);
+        }
+    }
+
+    private String getDeclaringClassName(Field field) {
+        return field.getDeclaringClass().getName();
+    }
+
+    private String getFieldTypeClassName(Field field) {
+        return field.getType().getName();
+    }
+
+    private void registerComponentInstanceMixins(Field field, 
Consumer<Class<?>> processClass, Consumer<String> processClassName) 
+    {
+        
+        if 
(field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class))
+        {
+            
+            MixinClasses mixinClasses = 
field.getAnnotation(MixinClasses.class);
+            if (mixinClasses != null)
+            {
+                for (Class dependency : mixinClasses.value()) 
+                {
+                    add(field.getDeclaringClass(), dependency);
+                    processClass.accept(dependency);
+                }
+            }
+            
+            Mixins mixins = field.getAnnotation(Mixins.class);
+            if (mixins != null)
+            {
+                for (String mixin : mixins.value())
+                {
+                    // Logic adapted from MixinsWorker
+                    Orderable<String> typeAndOrder = 
TapestryInternalUtils.mixinTypeAndOrder(mixin);
+                    final String dependency = 
resolver.resolveMixinTypeToClassName(typeAndOrder.getTarget());
+                    add(getDeclaringClassName(field), dependency);
+                    processClassName.accept(dependency);
+                }
+            }
+            
+        }
+                
     }
 
     @Override
     public void register(ComponentPageElement componentPageElement) 
     {
         final String componentClassName = getClassName(componentPageElement);
-
+        
         if (!alreadyProcessed.contains(componentClassName)) 
         {
             synchronized (map) 
@@ -255,6 +491,14 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
         }
     }
     
+    private void add(Class component, Class dependency) 
+    {
+        if (plasticManager.shouldInterceptClassLoading(dependency.getName()))
+        {
+            add(component.getName(), dependency.getName());
+        }
+    }
+    
     private void add(String component, String dependency) 
     {
         Objects.requireNonNull(component, "Parameter component cannot be 
null");
@@ -281,12 +525,18 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
     List<String> listen(List<String> resources)
     {
         List<String> furtherDependents;
-        if (resources.isEmpty())
+        if (!INVALIDATIONS_ENABLED.get())
+        {
+            furtherDependents = Collections.emptyList();
+        }
+        else if (resources.isEmpty())
         {
             clear();
             furtherDependents = Collections.emptyList();
         }
-        else
+        // Don't invalidate component dependency information when 
+        // PageClassloaderContextManager is merging contexts
+        else if (!pageClassloaderContextManager.isMerging())
         {
             furtherDependents = new ArrayList<>();
             for (String resource : resources) 
@@ -302,6 +552,10 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
                 clear(resource);
             }
         }
+        else
+        {
+            furtherDependents = Collections.emptyList();
+        }
         return furtherDependents;
     }
 
@@ -347,4 +601,187 @@ public class ComponentDependencyRegistryImpl implements 
ComponentDependencyRegis
                 .collect(Collectors.toSet());
     }
     
+    private boolean isTransformed(Class clasz)
+    {
+        return plasticManager.shouldInterceptClassLoading(clasz.getName());
+    }
+
+    @Override
+    public boolean isStoredDependencyInformationPresent() 
+    {
+        return storedDependencyInformationPresent;
+    }
+
+    @Override
+    public void disableInvalidations() 
+    {
+        INVALIDATIONS_ENABLED.set(false);
+    }
+
+    @Override
+    public void enableInvalidations() 
+    {
+        INVALIDATIONS_ENABLED.set(true);
+    }
+
+    /**
+     * Only really implemented method is {@link 
ComponentModel#getBaseResource()}
+     */
+    private class ComponentModelMock implements ComponentModel 
+    {
+        
+        final private Resource baseResource;
+        final private boolean isPage;
+        final private String componentClassName;
+        
+        public ComponentModelMock(Class<?> component, boolean isPage)
+        {
+            componentClassName = component.getName();
+            String templateLocation = componentClassName.replace('.', '/');
+            baseResource = new ClasspathResource(templateLocation);
+            
+            this.isPage = isPage;
+        }
+
+        @Override
+        public Resource getBaseResource() 
+        {
+            return baseResource;
+        }
+
+        @Override
+        public String getLibraryName() 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean isPage() 
+        {
+            return isPage;
+        }
+
+        @Override
+        public String getComponentClassName() 
+        {
+            return componentClassName;
+        }
+
+        @Override
+        public List<String> getEmbeddedComponentIds() 
+        {
+            return null;
+        }
+
+        @Override
+        public EmbeddedComponentModel getEmbeddedComponentModel(String 
componentId) 
+        {
+            return null;
+        }
+
+        @Override
+        public String getFieldPersistenceStrategy(String fieldName) 
+        {
+            return null;
+        }
+
+        @Override
+        public Logger getLogger() 
+        {
+            return null;
+        }
+
+        @Override
+        public List<String> getMixinClassNames() 
+        {
+            return null;
+        }
+
+        @Override
+        public ParameterModel getParameterModel(String parameterName) 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean isFormalParameter(String parameterName) 
+        {
+            return false;
+        }
+
+        @Override
+        public List<String> getParameterNames() 
+        {
+            return null;
+        }
+
+        @Override
+        public List<String> getDeclaredParameterNames() 
+        {
+            return null;
+        }
+
+        @Override
+        public List<String> getPersistentFieldNames() 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean isRootClass() 
+        {
+            return false;
+        }
+
+        @Override
+        public boolean getSupportsInformalParameters() 
+        {
+            return false;
+        }
+
+        @Override
+        public ComponentModel getParentModel() 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean isMixinAfter() 
+        {
+            return false;
+        }
+
+        @Override
+        public String getMeta(String key) 
+        {
+            return null;
+        }
+
+        @Override
+        public Set<Class> getHandledRenderPhases() 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean handlesEvent(String eventType) 
+        {
+            return false;
+        }
+
+        @Override
+        public String[] getOrderForMixin(String mixinClassName) 
+        {
+            return null;
+        }
+
+        @Override
+        public boolean handleActivationEventContext() 
+        {
+            return false;
+        }
+
+    }
+
+    
 }
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 a2ff8d87c..588c99dde 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
@@ -181,6 +181,11 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
         controlledPackageNames.addAll(configuration.keySet());
 
         initializeService();
+        
+        pageClassloaderContextManager.initialize(
+                rootPageClassloaderContext, 
+                
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+        
     }
 
     @PostInjection
@@ -231,6 +236,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
             }
         }
 
+        // TODO: fix this
 //        final Iterator<Entry<String, ComponentModel>> classToModelIterator = 
classToModel.entrySet().iterator();
 //        while (classToModelIterator.hasNext())
 //        {
@@ -317,8 +323,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
                         // 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, rootPageClassloaderContext, 
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+                        final PageClassloaderContext context = 
pageClassloaderContextManager.get(className);
                         final ClassInstantiator<Component> plasticInstantiator 
= 
                                 
context.getPlasticManager().getClassInstantiator(className);
 
@@ -512,8 +517,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
         {
             try
             {
-                final PageClassloaderContext context = 
pageClassloaderContextManager.get(
-                        typeName, rootPageClassloaderContext, 
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+                final PageClassloaderContext context = 
pageClassloaderContextManager.get(typeName);
                 return 
PlasticInternalUtils.toClass(context.getPlasticManager().getClassLoader(), 
typeName);
             } catch (ClassNotFoundException ex)
             {
@@ -717,7 +721,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
 
         @Override
         public PlasticManager getPlasticManager() {
-            throw new UnsupportedOperationException();
+            return 
rootPageClassloaderContext.getProxyFactory().getPlasticManager();
         }
         
         @Override
@@ -726,19 +730,8 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
             PageClassloaderContext context = 
rootPageClassloaderContext.findByClassName(className);
             if (context == null)
             {
-                context = pageClassloaderContextManager.get(
-                        className, rootPageClassloaderContext, 
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+                context = pageClassloaderContextManager.get(className);
             }
-//            System.out.println("Class: " + className);
-//            System.out.println("Root classloader    : " + 
rootPageClassloaderContext);
-//            System.out.println("Parent classloader  : " + parent);
-//            if (!rootPageClassloaderContext.getChildren().isEmpty())
-//            {
-//                System.out.println("Unknown classloader : " + 
rootPageClassloaderContext.getChildren().iterator().next());
-//            }
-//            System.out.println("Returned context    : " + context);
-//            System.out.println("Returned classloader: " + 
context.getProxyFactory().getClassLoader());
-//            System.out.println();
             return context.getProxyFactory();
         }
         
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 ec1b5a0a9..79b2a51dd 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
@@ -38,6 +38,8 @@ import org.apache.tapestry5.services.ComponentMessages;
 import org.apache.tapestry5.services.ComponentTemplates;
 import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
+import org.apache.tapestry5.services.pageload.PageClassloaderContext;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
 import org.slf4j.Logger;
 
 public class PageSourceImpl implements PageSource
@@ -50,6 +52,8 @@ public class PageSourceImpl implements PageSource
     
     private final ComponentClassResolver componentClassResolver;
     
+    private final PageClassloaderContextManager pageClassloaderContextManager;
+    
     private final Logger logger;
     
     final private boolean productionMode;
@@ -83,6 +87,13 @@ public class PageSourceImpl implements PageSource
 
             return pageName.equals(other.pageName) && 
selector.equals(other.selector);
         }
+
+        @Override
+        public String toString() {
+            return "CachedPageKey [pageName=" + pageName + ", selector=" + 
selector + "]";
+        }
+        
+        
     }
 
     private final Map<CachedPageKey, SoftReference<Page>> pageCache = 
CollectionFactory.newConcurrentMap();
@@ -90,6 +101,7 @@ public class PageSourceImpl implements PageSource
     public PageSourceImpl(PageLoader pageLoader, 
ComponentRequestSelectorAnalyzer selectorAnalyzer,
             ComponentDependencyRegistry componentDependencyRegistry,
             ComponentClassResolver componentClassResolver,
+            PageClassloaderContextManager pageClassloaderContextManager,
             @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
             Logger logger)
     {
@@ -98,6 +110,7 @@ public class PageSourceImpl implements PageSource
         this.componentDependencyRegistry = componentDependencyRegistry;
         this.componentClassResolver = componentClassResolver;
         this.productionMode = productionMode;
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
         this.logger = logger;
     }
 
@@ -126,7 +139,27 @@ 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.
 
-            page = pageLoader.loadPage(canonicalPageName, selector);
+//            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);
+//                }
+//                
+//            }
+            
+             page = pageLoader.loadPage(canonicalPageName, selector);
 
             ref = new SoftReference<Page>(page);
 
@@ -174,14 +207,15 @@ public class PageSourceImpl implements PageSource
             String pageName;
             for (String className : resources)
             {
-                pageName = componentClassResolver.getLogicalName(className);
-                if (pageName != null && !pageName.isEmpty())
+                if (componentClassResolver.isPage(className))
                 {
+                    pageName = 
componentClassResolver.resolvePageClassNameToPageName(className);
                     final Iterator<Entry<CachedPageKey, SoftReference<Page>>> 
iterator = pageCache.entrySet().iterator();
                     while (iterator.hasNext())
                     {
                         final Entry<CachedPageKey, SoftReference<Page>> entry 
= iterator.next();
-                        if 
(entry.getKey().pageName.equalsIgnoreCase(pageName)) 
+                        final String entryPageName = entry.getKey().pageName;
+                        if (entryPageName.equalsIgnoreCase(pageName)) 
                         {
                             logger.info("Clearing cached page '{}'", pageName);
                             iterator.remove();
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 30831509a..32fa090ef 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
@@ -20,6 +20,8 @@ import 
org.apache.tapestry5.services.ComponentEventLinkEncoder;
 import org.apache.tapestry5.services.ComponentEventRequestParameters;
 import org.apache.tapestry5.services.PageRenderRequestParameters;
 import org.apache.tapestry5.services.RequestExceptionHandler;
+import org.apache.tapestry5.services.pageload.PageClassloaderContext;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
 
 /**
  * Filter for the {@link org.apache.tapestry5.http.services.RequestHandler} 
pipeline used to intercept and report
@@ -32,18 +34,21 @@ public class RequestErrorFilter implements RequestFilter
     private final InvalidationEventHub classesInvalidationHub;
     private final ComponentEventLinkEncoder componentEventLinkEncoder;
     private final ComponentInstantiatorSource componentInstantiatorSource;
+    private final PageClassloaderContextManager pageClassloaderContextManager;
     private final static String QUERY_PARAMETER = 
"RequestErrorFilterRedirected";
     private final static Pattern CCE_PATTERN = Pattern.compile("((.*)\\scannot 
be cast to (.*))(.*)");
 
     public RequestErrorFilter(InternalRequestGlobals internalRequestGlobals, 
RequestExceptionHandler exceptionHandler,
             @ComponentClasses InvalidationEventHub classesInvalidationHub, 
ComponentEventLinkEncoder componentEventLinkEncoder,
-            ComponentInstantiatorSource componentInstantiatorSource)
+            ComponentInstantiatorSource componentInstantiatorSource, 
+            PageClassloaderContextManager pageClassloaderContextManager)
     {
         this.internalRequestGlobals = internalRequestGlobals;
         this.exceptionHandler = exceptionHandler;
         this.classesInvalidationHub = classesInvalidationHub;
         this.componentEventLinkEncoder = componentEventLinkEncoder;
         this.componentInstantiatorSource = componentInstantiatorSource;
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
     }
 
     public boolean service(Request request, Response response, RequestHandler 
handler) throws IOException
@@ -60,49 +65,51 @@ 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;
-                    }
-                    
-                }
-                
-            }
+//            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;
+//                    }
+//                    
+//                }
+//                
+//            }
             
             // 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
             // a NoClassDefFoundError with no cause; here we're trying to link 
the cause back in.
             // TAPESTRY-2078
+            
+            System.out.println("YYYYYY" + 
pageClassloaderContextManager.getRoot().toRecursiveString());
 
             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 457b40949..a0c06d4f5 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,20 +14,26 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry5.SymbolConstants;
 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.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;
 
-import java.util.Map;
-
 /**
  * In Tapestry 5.1, the implementation of this worked with the page pool (a 
pool of page instances, reserved
  * to individual requests/threads). Page pooling was deprecated in 5.2 and 
removed in 5.3.
@@ -36,6 +42,8 @@ import java.util.Map;
  */
 @Scope(ScopeConstants.PERTHREAD)
 public class RequestPageCacheImpl implements RequestPageCache, Runnable
+
+/// This should have a listener too!
 {
     private final Logger logger;
 
@@ -46,13 +54,25 @@ public class RequestPageCacheImpl implements 
RequestPageCache, Runnable
     private final RequestGlobals requestGlobals;
 
     private final Map<String, Page> cache = CollectionFactory.newMap();
-
-    public RequestPageCacheImpl(Logger logger, ComponentClassResolver 
resolver, PageSource pageSource, RequestGlobals requestGlobals)
+    
+    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)
     {
         this.logger = logger;
         this.resolver = resolver;
         this.pageSource = pageSource;
         this.requestGlobals = requestGlobals;
+        this.pageClassloaderContextManager = pageClassloaderContextManager;
+        this.productionMode = productionMode;
+        this.componentDependencyRegistry = componentDependencyRegistry;
     }
 
     @PostInjection
@@ -83,13 +103,69 @@ 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);
             }
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 7f9f6c26b..2240a418b 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
@@ -98,7 +98,8 @@ public class InjectComponentWorker implements 
ComponentClassTransformWorker2
                 if (embedded.getClass().getName().equals(className))
                 {
                     logger.warn(message);
-                    throw new DifferentClassVersionsException(message, 
className);
+                    throw new DifferentClassVersionsException(message, 
className, 
+                            fieldType.getClassLoader(), 
embedded.getClass().getClassLoader());
                 }
                 else
                 {
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/InternalModule.java 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/InternalModule.java
index 0b9b73806..122068aba 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/InternalModule.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/InternalModule.java
@@ -116,7 +116,7 @@ public class InternalModule
         binder.bind(AjaxFormUpdateController.class);
         binder.bind(ResourceDigestManager.class, 
ResourceDigestManagerImpl.class);  // Remove in Tapestry 5.5
         binder.bind(RequestPageCache.class, RequestPageCacheImpl.class);
-        binder.bind(ComponentInstantiatorSource.class);
+        binder.bind(ComponentInstantiatorSource.class).eagerLoad();
         binder.bind(InternalComponentInvalidationEventHub.class);
         binder.bind(PageSource.class, PageSourceImpl.class);
         binder.bind(PageLoader.class, PageLoaderImpl.class).preventReloading();
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 c4f5b4efe..9c4a55827 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
@@ -363,10 +363,11 @@ import 
org.apache.tapestry5.services.messages.PropertiesFileParser;
 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.PreloaderMode;
+import org.apache.tapestry5.services.rest.MappedEntityManager;
 import org.apache.tapestry5.services.rest.OpenApiDescriptionGenerator;
 import org.apache.tapestry5.services.rest.OpenApiTypeDescriber;
-import org.apache.tapestry5.services.rest.MappedEntityManager;
 import org.apache.tapestry5.services.security.ClientWhitelist;
 import org.apache.tapestry5.services.security.WhitelistAnalyzer;
 import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
@@ -517,7 +518,7 @@ public final class TapestryModule
         binder.bind(AjaxResponseRenderer.class, 
AjaxResponseRendererImpl.class);
         binder.bind(AlertManager.class, AlertManagerImpl.class);
         binder.bind(ValidationDecoratorFactory.class, 
ValidationDecoratorFactoryImpl.class);
-        binder.bind(PropertyConduitSource.class, 
PropertyConduitSourceImpl.class);
+        binder.bind(PropertyConduitSource.class, 
PropertyConduitSourceImpl.class).eagerLoad();
         binder.bind(ClientWhitelist.class, ClientWhitelistImpl.class);
         binder.bind(MetaDataLocator.class, MetaDataLocatorImpl.class);
         binder.bind(ComponentClassCache.class, ComponentClassCacheImpl.class);
@@ -2772,9 +2773,20 @@ public final class TapestryModule
     public static ComponentDependencyRegistry buildComponentDependencyRegistry(
             InternalComponentInvalidationEventHub 
internalComponentInvalidationEventHub,
             ResourceChangeTracker resourceChangeTracker,
-            ComponentTemplateSource componentTemplateSource)
-    {
-        ComponentDependencyRegistry componentDependencyRegistry = new 
ComponentDependencyRegistryImpl();
+            ComponentTemplateSource componentTemplateSource,
+            PageClassloaderContextManager pageClassloaderContextManager,
+            ComponentInstantiatorSource componentInstantiatorSource,
+            ComponentClassResolver componentClassResolver,
+            TemplateParser templateParser,
+            ComponentTemplateLocator componentTemplateLocator)
+    {
+        ComponentDependencyRegistry componentDependencyRegistry = 
+                new ComponentDependencyRegistryImpl(
+                        pageClassloaderContextManager,
+                        
componentInstantiatorSource.getProxyFactory().getPlasticManager(),
+                        componentClassResolver,
+                        templateParser,
+                        componentTemplateLocator);
         
componentDependencyRegistry.listen(internalComponentInvalidationEventHub);
         componentDependencyRegistry.listen(resourceChangeTracker);
         
componentDependencyRegistry.listen(componentTemplateSource.getInvalidationEventHub());
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
index e94489a5e..62e81f63d 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
@@ -188,4 +188,10 @@ public interface ComponentClassResolver
      */
     @IncompatibleChange(release = "5.8.3", details = "Added method")
     public String getLogicalName(String className);
+
+    /**
+     * Returns the class name for a page, component or class given its logical 
name.
+     */
+    @IncompatibleChange(release = "5.8.3", details = "Added method")
+    String getClassName(String logicalName);
 }
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 4fe237e34..1b17f69a9 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
@@ -13,6 +13,8 @@
 // limitations under the License.
 package org.apache.tapestry5.services.pageload;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
@@ -43,6 +45,12 @@ public class PageClassloaderContext
     private final PlasticManager plasticManager;
     
     private final PlasticProxyFactory proxyFactory;
+
+    /**
+     * 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, 
@@ -114,11 +122,22 @@ public class PageClassloaderContext
         classNames.add(className);
     }
     
-    public void addChildren(PageClassloaderContext context)
+    /**
+     * Adds a child context.
+     */
+    public void addChild(PageClassloaderContext context)
     {
         children.add(context);
     }
-    
+
+    /**
+     * Removes a child context.
+     */
+    public void removeChildren(PageClassloaderContext context)
+    {
+        children.remove(context);
+    }
+
     /**
      * Searches for the context that contains the given class in itself and 
recursivel in its children.
      */
@@ -156,32 +175,67 @@ public class PageClassloaderContext
      */
     public void invalidate() 
     {
-        LOGGER.info("Invalidating page classloader context '{}' (class loader 
{}, classes : {})", 
+        for (PageClassloaderContext child : new ArrayList<>(children)) 
+        {
+            child.invalidate();
+        }
+        LOGGER.debug("Invalidating page classloader context '{}' (class loader 
{}, classes : {})", 
                 name, proxyFactory.getClassLoader(), classNames);
         classNames.clear();
         parent.getChildren().remove(this);
         proxyFactory.clearCache();
-        for (PageClassloaderContext child : children) 
+    }
+
+    /**
+     * Returns whether this is the root context.
+     */
+    public boolean isRoot()
+    {
+        return parent == null;
+    }
+
+    /**
+     * Returns whether this is the <code>unknwon</code> context.
+     * @see #UNKOWN_CONTEXT_NAME
+     */
+    public boolean isUnknown()
+    {
+        return name.equals(UNKOWN_CONTEXT_NAME);
+    }
+    
+    /**
+     * Returns the set of descendents (children and their children recursively
+     * of this context.
+     */
+    public Set<PageClassloaderContext> getDescendents()
+    {
+        Set<PageClassloaderContext> descendents;
+        if (children.isEmpty())
         {
-            child.invalidate();
+            descendents = Collections.emptySet();
+        }
+        else
+        {
+            descendents = new HashSet<>(children);
+            for (PageClassloaderContext child : children) 
+            {
+                descendents.addAll(child.getDescendents());
+            }
         }
+        return descendents;
     }
 
     @Override
-    public int hashCode() 
-    {
+    public int hashCode() {
         return Objects.hash(name);
     }
-
+    
     @Override
-    public boolean equals(Object obj) 
-    {
-        if (this == obj) 
-        {
+    public boolean equals(Object obj) {
+        if (this == obj) {
             return true;
         }
-        if (!(obj instanceof PageClassloaderContext)) 
-        {
+        if (!(obj instanceof PageClassloaderContext)) {
             return false;
         }
         PageClassloaderContext other = (PageClassloaderContext) obj;
@@ -194,10 +248,30 @@ public class PageClassloaderContext
         return "PageClassloaderContext [name=" + name + 
                 ", parent=" + (parent != null ? parent.getName() : "null" ) + 
                 ", classLoader=" + 
afterAt(proxyFactory.getClassLoader().toString()) +
-                ", object id" + afterAt(super.toString()) +
-//                ", classNames=" + classNames + 
+                (isRoot() ? ""  : ", classNames=" + classNames) + 
                 "]";
     }
+    
+    public String toRecursiveString()
+    {
+        StringBuilder builder = new StringBuilder();
+        toRecursiveString(builder, "");
+        return builder.toString();
+    }
+    
+    private void toRecursiveString(StringBuilder builder, String tabs)
+    {
+        builder.append(tabs);
+        builder.append(name);
+        builder.append(" : ");
+        builder.append(afterAt(proxyFactory.getClassLoader().toString()));
+        builder.append(" : ");
+        builder.append(classNames);
+        builder.append("\n");
+        for (PageClassloaderContext child : children) {
+            child.toRecursiveString(builder, tabs + "\t");
+        }
+    }
 
     private static String afterAt(String string) 
     {
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 d91857444..8ead155d3 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,6 +17,7 @@ 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;
 
 /**
@@ -38,20 +39,44 @@ public interface PageClassloaderContextManager
      * {@linkplain} ClassLoader} and returns a new {@linkplain 
PlasticProxyFactory}.
      * @return the {@link PageClassloaderContext} associated with that class.
      */
-    PageClassloaderContext get(
-            String className, 
-            PageClassloaderContext root,
-            Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider);
+    PageClassloaderContext get(String className);
     
     /**
-     * Invalidates one page classloader context and returns a set containing 
the names
+     * Invalidates page classloader contexts and returns a set containing the 
names
      * of all classes that should be invalidated.
      */
-    Set<String> invalidate(PageClassloaderContext context);
+    Set<String> invalidate(PageClassloaderContext... contexts);
+    
+    /**
+     * Invalidates page classloader contexts and invalidates the classes in 
the context as well.
+     */
+    void invalidateAndFireInvalidationEvents(PageClassloaderContext... 
contexts);
+    
+    /**
+     * Returns the root context.
+     */
+    PageClassloaderContext getRoot();
     
     /**
      * Clears any state held by this manager.
      */
     void clear();
     
+    /**
+     * Returns whether contexts are being merged.
+     */
+    boolean isMerging();
+
+    /**
+     * Removes one specific class from this manager, invalidating the context 
where
+     * it is.
+     */
+    void clear(String className);
+
+    /**
+     * Initializes this service with the root context and a Plastic proxy 
factory provider.
+     * Method can only be called once. None of the parameters may be null.
+     */
+    void initialize(PageClassloaderContext root, Function<ClassLoader, 
PlasticProxyFactory> plasticProxyFactoryProvider);
+    
 }
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 e382f01b2..94771139b 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
@@ -13,16 +13,27 @@
 // limitations under the License.
 package org.apache.tapestry5.services.pageload;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import org.apache.tapestry5.SymbolConstants;
+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.services.ComponentClassResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default {@linkplain PageClassloaderContextManager} implementation.
@@ -32,26 +43,63 @@ import org.apache.tapestry5.ioc.annotations.Symbol;
 public class PageClassloaderContextManagerImpl implements 
PageClassloaderContextManager
 {
     
-    private static final String UNKOWN_CONTEXT_NAME = "unknown";
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PageClassloaderContextManager.class);
     
     private final ComponentDependencyRegistry componentDependencyRegistry;
     
+    private final ComponentClassResolver componentClassResolver;
+    
+    private final InternalComponentInvalidationEventHub invalidationHub;
+    
+    private final InvalidationEventHub componentClassesInvalidationEventHub;
+    
     private final boolean productionMode;
     
+    private final static ThreadLocal<Integer> NESTED_MERGE_COUNT = 
ThreadLocal.withInitial(() -> 0);
+    
+    private final static ThreadLocal<Boolean> INVALIDATING_CONTEXT = 
ThreadLocal.withInitial(() -> false);
+    
+    private static final AtomicInteger MERGED_COUNTER = new AtomicInteger(1);
+    
+    private Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider;
+    
+    private PageClassloaderContext root;
+    
     public PageClassloaderContextManagerImpl(
             final ComponentDependencyRegistry componentDependencyRegistry, 
+            final ComponentClassResolver componentClassResolver,
+            final InternalComponentInvalidationEventHub invalidationHub,
+            final @ComponentClasses InvalidationEventHub 
componentClassesInvalidationEventHub,
             @Symbol(SymbolConstants.PRODUCTION_MODE) final boolean 
productionMode) 
     {
         super();
         this.componentDependencyRegistry = componentDependencyRegistry;
+        this.componentClassResolver = componentClassResolver;
+        this.invalidationHub = invalidationHub;
+        this.componentClassesInvalidationEventHub = 
componentClassesInvalidationEventHub;
         this.productionMode = productionMode;
+        invalidationHub.addInvalidationCallback(this::listen);
+        NESTED_MERGE_COUNT.set(0);
     }
-
+    
     @Override
-    public PageClassloaderContext get(
-            final String className, 
+    public void initialize(
             final PageClassloaderContext root,
             final Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider)
+    {
+        if (this.root != null)
+        {
+            throw new 
IllegalStateException("PageClassloaderContextManager.initialize() can only be 
called once");
+        }
+        Objects.requireNonNull(root);
+        Objects.requireNonNull(plasticProxyFactoryProvider);
+        this.root = root;
+        this.plasticProxyFactoryProvider = plasticProxyFactoryProvider;
+        LOGGER.debug("Root context: {}", root);
+    }
+
+    @Override
+    public PageClassloaderContext get(final String className)
     {
         PageClassloaderContext context;
         
@@ -63,11 +111,30 @@ public class PageClassloaderContextManagerImpl implements 
PageClassloaderContext
         else
         {
             
-            context = root.findByClassName(className);
+            final String enclosingClassName = getEnclosingClassName(className);
+            context = root.findByClassName(enclosingClassName);
             
             if (context == null)
             {
-                context = processUsingDependencies(className, root, () -> 
getUnknownContext(root, plasticProxyFactoryProvider));
+                Set<String> classesToInvalidate = new HashSet<>();
+                
+                context = processUsingDependencies(
+                        enclosingClassName, 
+                        root, 
+                        () -> getUnknownContext(root, 
plasticProxyFactoryProvider),
+                        plasticProxyFactoryProvider,
+                        classesToInvalidate);
+                
+                if (!classesToInvalidate.isEmpty())
+                {
+                    invalidate(classesToInvalidate);
+                }
+
+                if (!className.equals(enclosingClassName))
+                {
+                    context.addClass(className);
+                }
+                
             }
             
         }
@@ -76,13 +143,23 @@ 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) {
         PageClassloaderContext unknownContext = null;
         
         for (PageClassloaderContext child : root.getChildren()) 
         {
-            if (child.getName().equals(UNKOWN_CONTEXT_NAME))
+            if 
(child.getName().equals(PageClassloaderContext.UNKOWN_CONTEXT_NAME))
             {
                 unknownContext = child;
                 break;
@@ -91,17 +168,34 @@ public class PageClassloaderContextManagerImpl implements 
PageClassloaderContext
         
         if (unknownContext == null)
         {
-            unknownContext = new PageClassloaderContext(UNKOWN_CONTEXT_NAME, 
root, 
+            unknownContext = new 
PageClassloaderContext(PageClassloaderContext.UNKOWN_CONTEXT_NAME, root, 
                     Collections.emptySet(), 
                     plasticProxyFactoryProvider.apply(root.getClassLoader()));
-            root.addChildren(unknownContext);
+            root.addChild(unknownContext);
+            LOGGER.debug("Unknown context: {}", unknownContext);
         }
         return unknownContext;
     }
+    
+    private PageClassloaderContext processUsingDependencies(
+            String className, 
+            PageClassloaderContext root, 
+            Supplier<PageClassloaderContext> unknownContextProvider, 
+            Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider, Set<String> classesToInvalidate) 
+    {
+        return processUsingDependencies(className, root, 
unknownContextProvider, plasticProxyFactoryProvider, classesToInvalidate, new 
HashSet<>());
+    }
 
-    private PageClassloaderContext processUsingDependencies(String className, 
PageClassloaderContext root, Supplier<PageClassloaderContext> 
unknownContextProvider) 
+
+    private PageClassloaderContext processUsingDependencies(
+            String className, 
+            PageClassloaderContext root, 
+            Supplier<PageClassloaderContext> unknownContextProvider, 
+            Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider, Set<String> classesToInvalidate,
+            Set<String> alreadyProcessed) 
     {
         PageClassloaderContext context = root.findByClassName(className);
+        Set<String> circularDependencies = new HashSet<>(1);
         if (context == null)
         {
             
@@ -110,33 +204,295 @@ public class PageClassloaderContextManagerImpl 
implements PageClassloaderContext
             if 
(!root.getPlasticManager().shouldInterceptClassLoading(className))
             {
                 context = root;
+            } else {
+                if (
+                        !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);
+                }
+                else 
+                {
+
+                    alreadyProcessed.add(className);
+
+                    // Sorting dependencies alphabetically so we have 
consistent results.
+                    List<String> dependencies = new 
ArrayList<>(componentDependencyRegistry.getDependencies(className));
+                    Collections.sort(dependencies);
+                    
+                    // Process dependencies depth-first
+                    for (String dependency : dependencies)
+                    {
+                        // Avoid infinite recursion loops
+                        if (!alreadyProcessed.contains(dependency))
+                        {
+                            processUsingDependencies(dependency, root, 
unknownContextProvider, 
+                                    plasticProxyFactoryProvider, 
classesToInvalidate, alreadyProcessed);
+                        }
+                    }
+                    
+                    // Collect context dependencies
+                    Set<PageClassloaderContext> contextDependencies = new 
HashSet<>();
+                    for (String dependency : dependencies) 
+                    {
+                        // Direct circular dependency
+                        if 
(componentDependencyRegistry.getDependencies(dependency).contains(className))
+                        {
+                            circularDependencies.add(dependency);
+                        }
+                        else
+                        {
+                            PageClassloaderContext dependencyContext = 
root.findByClassName(dependency);
+                            if (dependencyContext == null)
+                            {
+                                dependencyContext = 
processUsingDependencies(dependency, root, unknownContextProvider,
+                                        plasticProxyFactoryProvider, 
classesToInvalidate, alreadyProcessed);
+
+                            }
+                            contextDependencies.add(dependencyContext);
+                        }
+                    }
+                    
+                    if (contextDependencies.size() == 0)
+                    {
+                        context = new PageClassloaderContext(
+                                getContextName(className), 
+                                root, 
+                                Collections.singleton(className), 
+                                
plasticProxyFactoryProvider.apply(root.getClassLoader()));
+                    }
+                    else 
+                    {
+                        PageClassloaderContext parentContext;
+                        if (contextDependencies.size() == 1)
+                        {
+                            parentContext = 
contextDependencies.iterator().next();
+                        }
+                        else
+                        {
+                            parentContext = merge(contextDependencies, 
plasticProxyFactoryProvider, root, classesToInvalidate);
+                        }
+                        context = new PageClassloaderContext(
+                                getContextName(className), 
+                                parentContext, 
+                                Collections.singleton(className), 
+                                
plasticProxyFactoryProvider.apply(root.getClassLoader()));
+                    }
+                    
+                    context.getParent().addChild(context);
+
+                    LOGGER.debug("New context: {}", context);
+                    
+                }
             }
-            // If we don't have dependency information about this class, it 
goes
-            // into the "unknown" context.
-            else if (!componentDependencyRegistry.contains(className))
+            
+        }
+        context.addClass(className);
+        for (String circularDependency : circularDependencies) 
+        {
+            context.addClass(circularDependency);
+        }
+        return context;
+    }
+    
+    private PageClassloaderContext merge(
+            Set<PageClassloaderContext> contextDependencies,
+            Function<ClassLoader, PlasticProxyFactory> 
plasticProxyFactoryProvider,
+            PageClassloaderContext root, Set<String> classesToInvalidate) 
+    {
+        
+        NESTED_MERGE_COUNT.set(NESTED_MERGE_COUNT.get() + 1);
+        
+        if (LOGGER.isDebugEnabled())
+        {
+            
+            LOGGER.debug("Nested merge count going up to {}", 
NESTED_MERGE_COUNT.get());
+
+            String classes;
+            StringBuilder builder = new StringBuilder();
+            builder.append("Merging the following page classloader contexts 
into one:\n");
+            for (PageClassloaderContext context : contextDependencies) 
             {
-                context = unknownContextProvider.get();
+                classes = context.getClassNames().stream()
+                        .map(this::getContextName)
+                        .sorted()
+                        .collect(Collectors.joining(", "));
+                builder.append(String.format("\t%s (parent %s) (%s)\n", 
context.getName(), context.getParent().getName(), classes));
             }
-            // TODO: implement this correctly by creating and merging new 
contexts
-            // as needed.
-            else 
+            LOGGER.debug(builder.toString().trim());
+        }
+        
+        Set<PageClassloaderContext> allContextsIncludingDescendents = new 
HashSet<>();
+        for (PageClassloaderContext context : contextDependencies) 
+        {
+            allContextsIncludingDescendents.add(context);
+            allContextsIncludingDescendents.addAll(context.getDescendents());
+        }
+
+        PageClassloaderContext merged;
+        
+        // Collect the classes in these dependencies, then invalidate the 
contexts
+        
+        Set<PageClassloaderContext> furtherDependencies = new HashSet<>();
+        
+        Set<String> classNames = new HashSet<>();
+        
+        for (PageClassloaderContext context : contextDependencies) 
+        {
+            if (!context.isRoot())
             {
-                context = unknownContextProvider.get();
+                classNames.addAll(context.getClassNames());
+            }
+            final PageClassloaderContext parent = context.getParent();
+            // We don't want the merged context to have a further dependency 
on 
+            // the root context (it's not mergeable) nor on itself.
+            if (!parent.isRoot() && 
!allContextsIncludingDescendents.contains(parent))
+            {
+                furtherDependencies.add(parent);
             }
-            
         }
-        context.addClass(className);
-        return context;
+        
+        final List<PageClassloaderContext> contextsToInvalidate = 
contextDependencies.stream()
+            .filter(c -> !c.isRoot())
+            .collect(Collectors.toList());
+        
+        if (!contextsToInvalidate.isEmpty())
+        {
+            
classesToInvalidate.addAll(invalidate(contextsToInvalidate.toArray(new 
PageClassloaderContext[contextsToInvalidate.size()])));
+        }
+        
+        PageClassloaderContext parent;
+        
+        // No context dependencies, so parent is going to be the root one
+        if (furtherDependencies.size() == 0)
+        {
+            parent = root;
+        }
+        else 
+        {
+            // Single shared context dependency, so it's our parent
+            if (furtherDependencies.size() == 1)
+            {
+                parent = furtherDependencies.iterator().next();
+            }
+            // No single context dependency, so we'll need to recursively 
merge it
+            // so we can have a single parent.
+            else
+            {
+                parent = merge(furtherDependencies, 
plasticProxyFactoryProvider, root, classesToInvalidate);
+                LOGGER.debug("New context: {}", parent);
+            }
+        }
+        
+        merged = new PageClassloaderContext(
+            "merged " + MERGED_COUNTER.getAndIncrement(),
+            parent, 
+            classNames, 
+            plasticProxyFactoryProvider.apply(parent.getClassLoader()));
+        
+        parent.addChild(merged);
+        
+        NESTED_MERGE_COUNT.set(NESTED_MERGE_COUNT.get() - 1);
+        if (LOGGER.isDebugEnabled())
+        {
+            LOGGER.debug("Nested merge count going down to {}", 
NESTED_MERGE_COUNT.get());
+        }
+        
+        return merged;
+    }
+
+    @Override
+    public void clear(String className) 
+    {
+        final PageClassloaderContext context = root.findByClassName(className);
+        if (context != null)
+        {
+//            invalidationHub.fireInvalidationEvent(new 
ArrayList<>(invalidate(context)));
+            invalidate(context);
+        }
+    }
+
+    private String getContextName(String className)
+    {
+        String contextName = componentClassResolver.getLogicalName(className);
+        if (contextName == null)
+        {
+            contextName = className;
+        }
+        return contextName;
     }
 
     @Override
-    public Set<String> invalidate(PageClassloaderContext context) 
+    public Set<String> invalidate(PageClassloaderContext ... contexts) 
     {
         Set<String> classNames = new HashSet<>();
-        addClassNames(context, classNames);
-        context.invalidate();
+        for (PageClassloaderContext context : contexts) {
+            addClassNames(context, classNames);
+            context.invalidate();
+            if (context.getParent() != null)
+            {
+                context.getParent().removeChildren(context);
+            }
+        }
         return classNames;
     }
+    
+    private List<String> listen(List<String> resources)
+    {
+//        System.out.println("XXXXXX Before PageClassloaderContextManager 
listen: " + root.toRecursiveString());
+        if (INVALIDATING_CONTEXT.get())
+        {
+            return Collections.emptyList();
+        }
+        Set<PageClassloaderContext> contextsToInvalidate = new HashSet<>();
+        for (String resource : resources) 
+        {
+            PageClassloaderContext context = root.findByClassName(resource);
+            if (context != null && !context.isRoot())
+            {
+                contextsToInvalidate.add(context);
+            }
+        }
+        
+        Set<String> furtherResources = invalidate(contextsToInvalidate.toArray(
+                new PageClassloaderContext[contextsToInvalidate.size()]));
+        
+        // We don't want to invalidate resources more than once
+        furtherResources.removeAll(resources);
+        
+        return new ArrayList<>(furtherResources);
+    }
+
+    @Override
+    public void invalidateAndFireInvalidationEvents(PageClassloaderContext... 
contexts) {
+        final Set<String> classNames = invalidate(contexts);
+        INVALIDATING_CONTEXT.set(true);
+        invalidate(classNames);
+        INVALIDATING_CONTEXT.set(false);
+    }
+    
+    private void invalidate(Set<String> classesToInvalidate) {
+        if (!classesToInvalidate.isEmpty())
+        {
+            LOGGER.debug("Invalidating classes {}", classesToInvalidate);
+            INVALIDATING_CONTEXT.set(true);
+            final ArrayList<String> classesToInvalidateAsList = new 
ArrayList<>(classesToInvalidate);
+            invalidationHub.fireInvalidationEvent(classesToInvalidateAsList);
+            
componentClassesInvalidationEventHub.fireInvalidationEvent(classesToInvalidateAsList);
+            INVALIDATING_CONTEXT.set(false);
+        }
+    }
 
     private void addClassNames(PageClassloaderContext context, Set<String> 
classNames) {
         classNames.addAll(context.getClassNames());
@@ -145,6 +501,17 @@ public class PageClassloaderContextManagerImpl implements 
PageClassloaderContext
         }
     }
 
+    @Override
+    public PageClassloaderContext getRoot() {
+        return root;
+    }
+
+    @Override
+    public boolean isMerging() 
+    {
+        return NESTED_MERGE_COUNT.get() > 0;
+    }
+
     @Override
     public void clear() 
     {
diff --git 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
index cc7ae7056..078ac9f7d 100644
--- 
a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
+++ 
b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
@@ -43,6 +43,7 @@
             </t:actionlink>
         </t:if>
         <t:actionlink t:id="runGC" zone="pages" class="btn btn-default">Run 
the GC</t:actionlink>
+        <t:actionlink t:id="storeDependencyInformation" zone="pages" 
class="btn btn-warning">Store dependency information</t:actionlink>
     </div>
     
     
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
index 9214e2ddc..0d5007513 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
@@ -17,6 +17,7 @@ import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
 
 import java.util.Calendar;
 
@@ -33,6 +34,9 @@ public class Border
 
     @Inject
     private ComponentResources resources;
+    
+    @Inject @Property
+    private PageClassloaderContextManager pccm; 
 
     public static final int year;
 
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/InstanceMixinDependencies.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/InstanceMixinDependencies.java
new file mode 100644
index 000000000..53146858d
--- /dev/null
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/InstanceMixinDependencies.java
@@ -0,0 +1,30 @@
+// Copyright 2023 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.MixinClasses;
+import org.apache.tapestry5.annotations.Mixins;
+import org.apache.tapestry5.corelib.components.TextField;
+import org.apache.tapestry5.integration.app1.mixins.EchoValue;
+
+public class InstanceMixinDependencies
+{
+    @Component
+    @Mixins("echovalue2::before:echovalue3")
+    
@MixinClasses(value={EchoValue.class},order={"after:echovalue2;after:echovalue3"})
+    private TextField order3;
+
+}
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 ecb207c5f..c9b5b7505 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
@@ -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 b470b5cce..847494f96 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
@@ -14,17 +14,57 @@
 
 package org.apache.tapestry5.internal.services;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
 
 import java.io.File;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
-import org.testng.annotations.BeforeClass;
+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.AbstractTextField;
+import org.apache.tapestry5.corelib.components.ActionLink;
+import org.apache.tapestry5.corelib.components.Any;
+import org.apache.tapestry5.corelib.components.BeanEditForm;
+import org.apache.tapestry5.corelib.components.EventLink;
+import org.apache.tapestry5.corelib.components.If;
+import org.apache.tapestry5.corelib.components.TextField;
+import org.apache.tapestry5.corelib.components.Zone;
+import org.apache.tapestry5.corelib.mixins.RenderDisabled;
+import org.apache.tapestry5.integration.app1.components.Border;
+import org.apache.tapestry5.integration.app1.components.ErrorComponent;
+import org.apache.tapestry5.integration.app1.components.OuterAny;
+import 
org.apache.tapestry5.integration.app1.components.TextOnlyOnDisabledTextField;
+import org.apache.tapestry5.integration.app1.mixins.AltTitleDefault;
+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.BlockCaller;
+import org.apache.tapestry5.integration.app1.pages.BlockHolder;
+import 
org.apache.tapestry5.integration.app1.pages.EmbeddedComponentTypeConflict;
+import org.apache.tapestry5.integration.app1.pages.InstanceMixinDependencies;
+import org.apache.tapestry5.integration.app1.pages.MixinParameterDefault;
+import org.apache.tapestry5.internal.services.templates.DefaultTemplateLocator;
+import org.apache.tapestry5.ioc.internal.QuietOperationTracker;
+import org.apache.tapestry5.modules.TapestryModule;
+import org.apache.tapestry5.plastic.PlasticManager;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
+import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
+import org.easymock.EasyMock;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 /**
@@ -35,19 +75,78 @@ public class ComponentDependencyRegistryImplTest
     
     private ComponentDependencyRegistryImpl componentDependencyRegistry;
     
-    @BeforeClass
+    private PageClassloaderContextManager pageClassloaderContextManager;
+    
+    private ComponentClassResolver resolver;
+    
+    private PlasticManager plasticManager;
+    
+    private TemplateParser templateParser;
+    
+    @SuppressWarnings("deprecation")
+    private ComponentTemplateLocator componentTemplateLocator;
+    
+    @BeforeMethod
     public void setup()
     {
         assertFalse(
                 String.format("During testing, %s shouldn't exist", 
ComponentDependencyRegistry.FILENAME), 
                 new File(ComponentDependencyRegistry.FILENAME).exists());
-        componentDependencyRegistry = new ComponentDependencyRegistryImpl();
+        
+        MockMappedConfiguration<String, URL> templateConfiguration = new 
MockMappedConfiguration<String, URL>();
+        TapestryModule.contributeTemplateParser(templateConfiguration);
+        templateParser = new TemplateParserImpl(templateConfiguration.map, 
false, new QuietOperationTracker());
+        
+        componentTemplateLocator = new DefaultTemplateLocator();
+        
+        resolver = EasyMock.createMock(ComponentClassResolver.class);
+        
+        expectResolveComponent(TextField.class);
+        expectResolveComponent(Border.class);
+        expectResolveComponent(BeanEditForm.class);
+        expectResolveComponent(Zone.class);
+        expectResolveComponent(ActionLink.class);
+        expectResolveComponent(If.class);
+        expectResolveComponent(ErrorComponent.class);
+        expectResolveComponent(EventLink.class);        
+        
+        
EasyMock.expect(resolver.resolveMixinTypeToClassName("textonlyondisabled"))
+            .andReturn(TextOnlyOnDisabled.class.getName()).anyTimes();
+        EasyMock.expect(resolver.resolveMixinTypeToClassName("echovalue2"))
+            .andReturn(EchoValue2.class.getName()).anyTimes();
+        
EasyMock.expect(resolver.resolveMixinTypeToClassName("alttitledefault"))
+            .andReturn(AltTitleDefault.class.getName()).anyTimes();
+        
+        pageClassloaderContextManager = 
EasyMock.createMock(PageClassloaderContextManager.class);
+        plasticManager = EasyMock.createMock(PlasticManager.class);
+        
EasyMock.expect(plasticManager.shouldInterceptClassLoading(EasyMock.anyString()))
+            .andAnswer(() -> {
+                String className = (String) EasyMock.getCurrentArguments()[0];
+                return className.contains(".pages.") || 
className.contains(".mixins.") ||
+                        className.contains(".components.") || 
className.contains(".base.");
+            }).anyTimes();
+        componentDependencyRegistry = new ComponentDependencyRegistryImpl(
+                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();
+    }
+    
+    private void configurePCCM(boolean merging)
+    {
+        EasyMock.reset(pageClassloaderContextManager);
+        
EasyMock.expect(pageClassloaderContextManager.isMerging()).andReturn(merging).anyTimes();
+        EasyMock.replay(pageClassloaderContextManager);
     }
     
     @Test(timeOut = 5000)
     public void listen()
     {
-        
         add("foo", "bar");
         add("d", "a");
         add("dd", "aa");
@@ -55,12 +154,21 @@ public class ComponentDependencyRegistryImplTest
         add("dd", "a");
         
         final List<String> resources = Arrays.asList("a", "aa", "none");
-        final List<String> result = 
componentDependencyRegistry.listen(resources);
+        
+        configurePCCM(true);
+        List<String> result = componentDependencyRegistry.listen(resources);
+        assertEquals(0, result.size());
+        
+        configurePCCM(false);
+        result = componentDependencyRegistry.listen(resources);
         Collections.sort(result);
         assertEquals(result, Arrays.asList("d", "dd"));
+        assertEquals("bar", 
componentDependencyRegistry.getDependencies("foo").iterator().next());
+        assertEquals("foo", 
componentDependencyRegistry.getDependents("bar").iterator().next());        
         
-        final List<String> returnValue = 
componentDependencyRegistry.listen(Collections.emptyList());
-        assertEquals(returnValue, Collections.emptyList());
+        configurePCCM(false);
+        result = componentDependencyRegistry.listen(Collections.emptyList());
+        assertEquals(result, Collections.emptyList());
         assertEquals(componentDependencyRegistry.getDependents("bar").size(), 
0);
         
assertEquals(componentDependencyRegistry.getDependencies("foo").size(), 0);
         
@@ -77,11 +185,15 @@ public class ComponentDependencyRegistryImplTest
         final String fulano = "a";
         final String beltrano = "aa";
         
-        assertEquals(componentDependencyRegistry.getDependencies(foo), 
Collections.emptySet(), 
-                "getDependents() should never return null");
+        assertEquals(
+                "getDependents() should never return null", 
+                Collections.emptySet(),
+                componentDependencyRegistry.getDependencies(foo));
 
-        assertEquals(componentDependencyRegistry.getDependents(foo), 
Collections.emptySet(), 
-                "getDependents() should never return null");
+        assertEquals(
+                "getDependents() should never return null", 
+                Collections.emptySet(),
+                componentDependencyRegistry.getDependents(foo));
 
         add(foo, bar);
         add(something, fulano);
@@ -92,17 +204,17 @@ public class ComponentDependencyRegistryImplTest
         add(fulano, null);
         add(beltrano, null);
         
-        assertEquals(componentDependencyRegistry.getDependencies(other), new 
HashSet<>(Arrays.asList(fulano, beltrano)));
-        assertEquals(componentDependencyRegistry.getDependencies(something), 
new HashSet<>(Arrays.asList(fulano)));
-        assertEquals(componentDependencyRegistry.getDependencies(fulano), new 
HashSet<>(Arrays.asList()));
-        assertEquals(componentDependencyRegistry.getDependencies(foo), new 
HashSet<>(Arrays.asList(bar)));
-        assertEquals(componentDependencyRegistry.getDependencies(bar), new 
HashSet<>(Arrays.asList()));
+        assertEquals(new HashSet<>(Arrays.asList(fulano, beltrano)), 
componentDependencyRegistry.getDependencies(other));
+        assertEquals(new HashSet<>(Arrays.asList(fulano)), 
componentDependencyRegistry.getDependencies(something));
+        assertEquals(new HashSet<>(Arrays.asList()), 
componentDependencyRegistry.getDependencies(fulano));
+        assertEquals(new HashSet<>(Arrays.asList(bar)), 
componentDependencyRegistry.getDependencies(foo));
+        assertEquals(new HashSet<>(Arrays.asList()), 
componentDependencyRegistry.getDependencies(bar));
 
-        assertEquals(componentDependencyRegistry.getDependents(bar), new 
HashSet<>(Arrays.asList(foo)));
-        assertEquals(componentDependencyRegistry.getDependents(fulano), new 
HashSet<>(Arrays.asList(other, something)));
-        assertEquals(componentDependencyRegistry.getDependents(foo), new 
HashSet<>(Arrays.asList()));
+        assertEquals(new HashSet<>(Arrays.asList(foo)), 
componentDependencyRegistry.getDependents(bar));
+        assertEquals(new HashSet<>(Arrays.asList(other, something)), 
componentDependencyRegistry.getDependents(fulano));
+        assertEquals(new HashSet<>(Arrays.asList()), 
componentDependencyRegistry.getDependents(foo));
         
-        assertEquals(componentDependencyRegistry.getRootClasses(), new 
HashSet<>(Arrays.asList(bar, fulano, beltrano)));
+        assertEquals(new HashSet<>(Arrays.asList(bar, fulano, beltrano)), 
componentDependencyRegistry.getRootClasses());
         
         assertTrue(componentDependencyRegistry.contains(foo));
         assertTrue(componentDependencyRegistry.contains(bar));
@@ -122,9 +234,115 @@ public class ComponentDependencyRegistryImplTest
         
     }
     
+    @Test
+    public void register()
+    {
+        
+        componentDependencyRegistry.clear();
+        
+        // Superclass
+        componentDependencyRegistry.register(EventLink.class);
+
+        // Superclass, recursively
+        assertDependencies(AbstractComponentEventLink.class, 
AbstractLink.class);
+        assertDependencies(AbstractLink.class);        
+        
+        // @InjectPage and circular dependencies
+        componentDependencyRegistry.register(BlockCaller.class);
+        assertDependencies(BlockCaller.class, BlockHolder.class);
+        assertDependencies(BlockHolder.class, BlockCaller.class);        
+        
+        // @InjectComponent 
+        // Components declared in templates
+        componentDependencyRegistry.register(AlertsDemo.class);
+        assertDependencies(AlertsDemo.class, Zone.class, // @InjectComponent
+                // Components declared in template
+                Border.class, BeanEditForm.class, Zone.class, If.class, 
+                ActionLink.class, ErrorComponent.class, EventLink.class);
+        
+        // Mixins defined in in templates (t:mixins="...").
+        componentDependencyRegistry.register(MixinParameterDefault.class);
+        assertDependencies(MixinParameterDefault.class, AltTitleDefault.class, 
// Mixin
+                ActionLink.class, Border.class); // Components declared in 
template);
+        
+        // @Component, type() not defined
+        componentDependencyRegistry.register(OuterAny.class);
+        assertDependencies(OuterAny.class, Any.class);
+        
+        // @Component, type() defined
+        
componentDependencyRegistry.register(EmbeddedComponentTypeConflict.class);
+        assertDependencies(EmbeddedComponentTypeConflict.class, 
TextField.class);
+
+        // @Mixin, type() not defined
+        componentDependencyRegistry.register(AbstractTextField.class);
+        assertDependencies(AbstractTextField.class, 
+                RenderDisabled.class, AbstractField.class);
+
+        // @Mixin, type() defined
+        
componentDependencyRegistry.register(TextOnlyOnDisabledTextField.class);
+        assertDependencies(TextOnlyOnDisabledTextField.class, 
+                TextOnlyOnDisabled.class, TextField.class);
+        
+        // @MixinClasses and @Mixins
+        componentDependencyRegistry.register(InstanceMixinDependencies.class);
+        assertDependencies(InstanceMixinDependencies.class, 
+                EchoValue.class, EchoValue2.class, TextField.class);
+        
+
+    }
+
+    private void assertDependencies(Class clasz, Class... dependencies) {
+        assertEquals(
+                setOf(dependencies),
+                componentDependencyRegistry.getDependencies(clasz.getName()));
+    }
+
+    private static Set<String> setOf(Class ... classes)
+    {
+        return Arrays.asList(classes).stream()
+            .map(Class::getName)
+            .collect(Collectors.toSet());
+    }
+
+    private static Set<String> setOf(String ... strings)
+    {
+        return new HashSet<>(Arrays.asList(strings));
+    }
+    
     private void add(String component, String dependency)
     {
         componentDependencyRegistry.add(component, dependency, true);
     }
 
+    private static final class MockMappedConfiguration<String, URL> implements 
MappedConfiguration<String, URL>
+    {
+        
+        private final Map<String, URL> map = new HashMap<>();
+
+        @Override
+        public void add(String key, URL value) 
+        {
+            map.put(key, value);
+        }
+
+        @Override
+        public void override(String key, URL value) 
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void addInstance(String key, Class<? extends URL> clazz) 
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void overrideInstance(String key, Class<? extends URL> clazz) 
+        {
+            throw new UnsupportedOperationException();            
+        }
+        
+    }
+    
 }
diff --git a/tapestry-core/src/test/resources/log4j.properties 
b/tapestry-core/src/test/resources/log4j.properties
index 7090fd495..6e07ce841 100644
--- a/tapestry-core/src/test/resources/log4j.properties
+++ b/tapestry-core/src/test/resources/log4j.properties
@@ -8,6 +8,8 @@ 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
 
diff --git 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/Border.tml
 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/Border.tml
index 007f7a017..2a2941e0e 100644
--- 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/Border.tml
+++ 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/Border.tml
@@ -44,6 +44,7 @@
         <t:devtool class="pull-right"/>
 
         <t:remove>alert-class is for the AlertsTest.check_informal_parameters 
test</t:remove>
+        <pre>${pccm.root.toRecursiveString().trim()}</pre>
         <t:alerts class="alert-class"/>
 
         <t:body/>

Reply via email to