You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2013/09/11 21:28:41 UTC

git commit: Centralize logic for forcing a reload Add options to the default ExceptionReport page to jump back to the failed page (w/ optional reload)

Updated Branches:
  refs/heads/master 954c905d0 -> 7419eed30


Centralize logic for forcing a reload
Add options to the default ExceptionReport page to jump back to the failed page (w/ optional reload)


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/7419eed3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/7419eed3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/7419eed3

Branch: refs/heads/master
Commit: 7419eed304339bb2ef71aa7e1b28ff10cf91c50c
Parents: 954c905
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Sep 11 12:27:48 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Wed Sep 11 12:28:32 2013 -0700

----------------------------------------------------------------------
 .../tapestry5/corelib/components/DevTool.java   |  24 +--
 .../corelib/pages/ExceptionReport.java          |  60 ++++++-
 .../tapestry5/corelib/pages/PageCatalog.java    |  33 +---
 .../services/ComponentMessagesSourceImpl.java   |  36 ++--
 .../services/ComponentTemplateSourceImpl.java   |  45 +++--
 .../internal/services/ForceReload.java          |  25 +++
 ...ternalComponentInvalidationEventHubImpl.java |  17 +-
 .../internal/services/MessagesSourceImpl.java   |  19 ++-
 .../internal/services/ReloadHelper.java         |  35 ++++
 .../internal/services/ReloadHelperImpl.java     |  60 +++++++
 .../tapestry5/modules/InternalModule.java       |   3 +-
 .../tapestry5/corelib/components/DevTool.tml    |   2 +-
 .../tapestry5/corelib/pages/ExceptionReport.tml | 171 +++++++++++--------
 .../tapestry5/corelib/pages/PageCatalog.tml     |  12 +-
 14 files changed, 388 insertions(+), 154 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
index 470a826..320e1cd 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
@@ -22,7 +22,7 @@ import org.apache.tapestry5.annotations.Component;
 import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.Property;
-import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
+import org.apache.tapestry5.internal.services.ReloadHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.Request;
@@ -33,6 +33,16 @@ import org.apache.tapestry5.services.javascript.JavaScriptSupport;
  * Renders a dropdown menu of useful options when developing. By default, the DevTool is disabled (invisible)
  * during production.
  * <p/>
+ * The DevTool provides the following options:
+ * <ul>
+ * <li>Reset the page state, discarding any persistent page properties</li>
+ * <li>Kill the HttpSession (discarding any server-side state)</li>
+ * <li>Re-render the page (useful after changing the page or template)</li>
+ * <li>Re-render the page with rendering comments</li>
+ * <li>Reload all pages and components: classes, messages, templates</li>
+ * <li>Open the T5 Dashboard page in a new window</li>
+ * </ul>
+ * <p/>
  * Note that due to conflicts between Prototype and jQuery, the dev tool is hidden after selecting an item from the menu.
  *
  * @tapestrydoc
@@ -85,7 +95,7 @@ public class DevTool
     private ComponentResources resources;
 
     @Inject
-    private ComponentInstantiatorSource componentInstantiatorSource;
+    private ReloadHelper reloadHelper;
 
     public String getZoneElement()
     {
@@ -153,15 +163,7 @@ public class DevTool
 
     Object onActionFromReload()
     {
-        if (productionMode)
-        {
-            alertManager.error("Forcing a class reload is only allowed when executing in development mode.");
-            return null;
-        }
-
-        componentInstantiatorSource.forceComponentInvalidation();
-
-        alertManager.info("Forced a component class reload.");
+        reloadHelper.forceReload();
 
         return devmodezone.getBody();
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index 954d4db..4a2bbc2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2012 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,17 +14,21 @@
 
 package org.apache.tapestry5.corelib.pages;
 
+import org.apache.tapestry5.EventContext;
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.alerts.AlertManager;
 import org.apache.tapestry5.annotations.ContentType;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.internal.services.PageActivationContextCollector;
+import org.apache.tapestry5.internal.services.ReloadHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.services.ExceptionReporter;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Session;
+import org.apache.tapestry5.services.*;
 
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -72,13 +76,61 @@ public class ExceptionReport implements ExceptionReporter
     @Property
     private String propertyName;
 
+    @Property
+    private String failurePage;
+
+    @Inject
+    private RequestGlobals requestGlobals;
+
+    @Inject
+    private AlertManager alertManager;
+
+    @Inject
+    private PageActivationContextCollector pageActivationContextCollector;
+
+    @Inject
+    private PageRenderLinkSource linkSource;
+
+    @Inject
+    private BaseURLSource baseURLSource;
+
+    @Inject
+    private ReloadHelper reloadHelper;
+
+    @Property
+    private String rootURL;
+
     private final String pathSeparator = System.getProperty(PATH_SEPARATOR_PROPERTY);
 
     public void reportException(Throwable exception)
     {
         rootException = exception;
+
+        failurePage = request.isXHR() ? null : requestGlobals.getActivePageName();
+
+        rootURL = baseURLSource.getBaseURL(request.isSecure());
+    }
+
+    public Object[] getReloadContext()
+    {
+        return pageActivationContextCollector.collectPageActivationContext(failurePage);
     }
 
+    Object onActionFromReloadFirst(EventContext reloadContext)
+    {
+        reloadHelper.forceReload();
+
+        return linkSource.createPageRenderLinkWithContext(request.getParameter("loadPage"), reloadContext);
+    }
+
+    Object onActionFromReloadRoot() throws MalformedURLException
+    {
+        reloadHelper.forceReload();
+
+        return new URL(baseURLSource.getBaseURL(request.isSecure()));
+    }
+
+
     public boolean getHasSession()
     {
         return request.getSession(false) != null;

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
index ff9f66b..9576b47 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
@@ -25,8 +25,8 @@ import org.apache.tapestry5.beaneditor.Validate;
 import org.apache.tapestry5.corelib.components.Zone;
 import org.apache.tapestry5.func.*;
 import org.apache.tapestry5.internal.PageCatalogTotals;
-import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
 import org.apache.tapestry5.internal.services.PageSource;
+import org.apache.tapestry5.internal.services.ReloadHelper;
 import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.OperationTracker;
@@ -88,7 +88,7 @@ public class PageCatalog
     private OperationTracker operationTracker;
 
     @Inject
-    private ComponentInstantiatorSource componentInstantiatorSource;
+    private ReloadHelper reloadHelper;
 
     @Inject
     private BeanModelSource beanModelSource;
@@ -156,23 +156,6 @@ public class PageCatalog
         return pageSource.getAllPages();
     }
 
-    Object onActionFromReloadClasses()
-    {
-        if (productionMode)
-        {
-            alertManager.error("Forcing a class reload is only allowed when executing in development mode.");
-            return null;
-        }
-
-        pageName = null;
-
-        componentInstantiatorSource.forceComponentInvalidation();
-
-        alertManager.info("Forced a component class reload.");
-
-        return pagesZone.getBody();
-    }
-
     Object onSuccessFromSinglePageLoad()
     {
         boolean found = !F.flow(getPages()).filter(new Predicate<Page>()
@@ -281,20 +264,12 @@ public class PageCatalog
         return pagesZone.getBody();
     }
 
-    Object onActionFromClearCache()
+    Object onActionFromClearCaches()
     {
-        if (productionMode)
-        {
-            alertManager.error("Clearing the cache is only allowed in development mode.");
-            return null;
-        }
-
-        pageSource.clearCache();
+        reloadHelper.forceReload();
 
         failures = null;
 
-        alertManager.info("Page cache cleared.");
-
         return pagesZone.getBody();
     }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
index 47837bb..8f0fa00 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,17 +14,11 @@
 
 package org.apache.tapestry5.internal.services;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.func.Worker;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
 import org.apache.tapestry5.ioc.annotations.Symbol;
-import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
 import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
 import org.apache.tapestry5.model.ComponentModel;
@@ -35,9 +29,13 @@ import org.apache.tapestry5.services.messages.PropertiesFileParser;
 import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
 public class ComponentMessagesSourceImpl implements ComponentMessagesSource, UpdateListener
 {
-    private final MessagesSource messagesSource;
+    private final MessagesSourceImpl messagesSource;
 
     private final MessagesBundle appCatalogBundle;
 
@@ -72,20 +70,20 @@ public class ComponentMessagesSourceImpl implements ComponentMessagesSource, Upd
     }
 
     public ComponentMessagesSourceImpl(@Symbol(SymbolConstants.PRODUCTION_MODE)
-    boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser,
-            ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter)
+                                       boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser,
+                                       ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter)
     {
         this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter));
     }
 
     ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource,
-            ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
+                                ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
     {
         this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker);
     }
 
     ComponentMessagesSourceImpl(boolean productionMode, List<Resource> appCatalogResources,
-            ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
+                                ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
     {
         messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, resourceLocator,
                 parser);
@@ -93,6 +91,18 @@ public class ComponentMessagesSourceImpl implements ComponentMessagesSource, Upd
         appCatalogBundle = createAppCatalogBundle(appCatalogResources);
     }
 
+    @PostInjection
+    public void setupReload(ReloadHelper reloadHelper)
+    {
+        reloadHelper.addReloadCallback(new Runnable()
+        {
+            public void run()
+            {
+                messagesSource.invalidate();
+            }
+        });
+    }
+
     public void checkForUpdates()
     {
         messagesSource.checkForUpdates();

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
index bbf7ed9..d651625 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,11 +14,6 @@
 
 package org.apache.tapestry5.internal.services;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
@@ -41,6 +36,11 @@ import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
 
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
 /**
  * Service implementation that manages a cache of parsed component templates.
  */
@@ -99,15 +99,15 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl
     };
 
     public ComponentTemplateSourceImpl(@Inject
-    @Symbol(SymbolConstants.PRODUCTION_MODE)
-    boolean productionMode, TemplateParser parser, ComponentResourceLocator locator,
-            ClasspathURLConverter classpathURLConverter)
+                                       @Symbol(SymbolConstants.PRODUCTION_MODE)
+                                       boolean productionMode, TemplateParser parser, ComponentResourceLocator locator,
+                                       ClasspathURLConverter classpathURLConverter)
     {
         this(productionMode, parser, locator, new URLChangeTracker(classpathURLConverter));
     }
 
     ComponentTemplateSourceImpl(boolean productionMode, TemplateParser parser, ComponentResourceLocator locator,
-            URLChangeTracker tracker)
+                                URLChangeTracker tracker)
     {
         super(productionMode);
 
@@ -122,6 +122,18 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl
         hub.addUpdateListener(this);
     }
 
+    @PostInjection
+    public void setupReload(ReloadHelper helper)
+    {
+        helper.addReloadCallback(new Runnable()
+        {
+            public void run()
+            {
+                invalidate();
+            }
+        });
+    }
+
     public ComponentTemplate getTemplate(ComponentModel componentModel, ComponentResourceSelector selector)
     {
         String componentName = componentModel.getComponentClassName();
@@ -206,13 +218,18 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl
     {
         if (tracker.containsChanges())
         {
-            tracker.clear();
-            templateResources.clear();
-            templates.clear();
-            fireInvalidationEvent();
+            invalidate();
         }
     }
 
+    private void invalidate()
+    {
+        tracker.clear();
+        templateResources.clear();
+        templates.clear();
+        fireInvalidationEvent();
+    }
+
     public InvalidationEventHub getInvalidationEventHub()
     {
         return this;

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ForceReload.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ForceReload.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ForceReload.java
new file mode 100644
index 0000000..2cb7ba1
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ForceReload.java
@@ -0,0 +1,25 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services;
+
+/**
+ * Forces a reload of all caches and invalidates the component class cache. This is only allowed
+ *
+ * @since 5.4
+ */
+public interface ForceReload
+{
+    boolean forceReload();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java
index 2c52c31..fe43063 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2011-2013 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.
@@ -16,17 +16,30 @@ package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 
 public class InternalComponentInvalidationEventHubImpl extends InvalidationEventHubImpl implements
         InternalComponentInvalidationEventHub
 {
     public InternalComponentInvalidationEventHubImpl(@Symbol(SymbolConstants.PRODUCTION_MODE)
-    boolean productionMode)
+                                                     boolean productionMode)
     {
         super(productionMode);
     }
 
+    @PostInjection
+    public void setupReload(ReloadHelper helper)
+    {
+        helper.addReloadCallback(new Runnable()
+        {
+            public void run()
+            {
+                fireInvalidationEvent();
+            }
+        });
+    }
+
     public void classInControlledPackageHasChanged()
     {
         fireInvalidationEvent();

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
index 119bee6..e2238e7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2006-2013 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.
@@ -83,14 +83,19 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess
     {
         if (tracker != null && tracker.containsChanges())
         {
-            messagesByBundleIdAndSelector.clear();
-            cookedProperties.clear();
-            rawProperties.clear();
+            invalidate();
+        }
+    }
 
-            tracker.clear();
+    public void invalidate()
+    {
+        messagesByBundleIdAndSelector.clear();
+        cookedProperties.clear();
+        rawProperties.clear();
 
-            fireInvalidationEvent();
-        }
+        tracker.clear();
+
+        fireInvalidationEvent();
     }
 
     public Messages getMessages(MessagesBundle bundle, ComponentResourceSelector selector)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java
new file mode 100644
index 0000000..53b44ec
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java
@@ -0,0 +1,35 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services;
+
+/**
+ * Forces a reload of all caches and invalidates the component class cache. This is only allowed
+ *
+ * @since 5.4
+ */
+public interface ReloadHelper
+{
+    /**
+     * Force a reload (if in production mode). Writes an {@link org.apache.tapestry5.alerts.AlertManager} alert message.
+     */
+    void forceReload();
+
+    /**
+     * Adds a callback to be invoked from {@link #forceReload()}.
+     *
+     * @param callback
+     */
+    void addReloadCallback(Runnable callback);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelperImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelperImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelperImpl.java
new file mode 100644
index 0000000..d2c669d
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelperImpl.java
@@ -0,0 +1,60 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.alerts.AlertManager;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+
+import java.util.List;
+
+public class ReloadHelperImpl implements ReloadHelper
+{
+    private final AlertManager alertManager;
+
+    private final boolean productionMode;
+
+    private final List<Runnable> callbacks = CollectionFactory.newThreadSafeList();
+
+    public ReloadHelperImpl(AlertManager alertManager, @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode)
+    {
+        this.alertManager = alertManager;
+        this.productionMode = productionMode;
+    }
+
+    public void forceReload()
+    {
+        if (productionMode)
+        {
+            alertManager.error("Can not force a reload in production mode.");
+            return;
+        }
+
+        for (Runnable c : callbacks)
+        {
+            c.run();
+        }
+
+        alertManager.info("Component classes, templates, and messages reloaded.");
+    }
+
+    public void addReloadCallback(Runnable callback)
+    {
+        assert callback != null;
+
+        callbacks.add(callback);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/java/org/apache/tapestry5/modules/InternalModule.java
----------------------------------------------------------------------
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 f5e190b..5067a33 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
@@ -69,7 +69,8 @@ public class InternalModule
         binder.bind(InternalComponentInvalidationEventHub.class);
         binder.bind(PageSource.class, PageSourceImpl.class);
         binder.bind(PageLoader.class, PageLoaderImpl.class).preventReloading();
-		binder.bind(UnknownActivationContextHandler.class, UnknownActivationContextHandlerImpl.class);
+        binder.bind(UnknownActivationContextHandler.class, UnknownActivationContextHandlerImpl.class);
+        binder.bind(ReloadHelper.class, ReloadHelperImpl.class);
     }
 
     public static CookieSource buildCookieSource(final RequestGlobals requestGlobals)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DevTool.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DevTool.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DevTool.tml
index 6142748..3e073c3 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DevTool.tml
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DevTool.tml
@@ -28,7 +28,7 @@
         <li class="divider"/>
         <t:if test="!productionMode">
             <li>
-                <t:actionlink zone="^" t:id="reload">Reload Component Classes</t:actionlink>
+                <t:actionlink zone="^" t:id="reload">Reload All Pages</t:actionlink>
             </li>
         </t:if>
         <li>

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
index abac44b..1ae084c 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
@@ -1,79 +1,116 @@
 <html xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
-<head>
-    <title>Application Exception</title>
-</head>
-<body>
-<div class="navbar navbar-inverse navbar-fixed-top">
-    <div class="container">
-        <div class="navbar-header">
-            <ul class="nav navbar-nav">
-                <li>
-                    <h4 class="navbar-text">Application Exception</h4>
-                </li>
-                <li class="divider-vertical"/>
-                <li>
-                    <p class="navbar-text">
-                        Tapestry Version: ${tapestryVersion}
-                    </p>
-                </li>
-                <li class="divider-vertical"/>
-                <li>
-                    <p class="navbar-text">
-                        Application Version: ${applicationVersion}
-                    </p>
-                </li>
-            </ul>
+    <head>
+        <title>Application Exception</title>
+    </head>
+    <body>
+        <div class="navbar navbar-inverse navbar-fixed-top">
+            <div class="container">
+                <div class="navbar-header">
+                    <ul class="nav navbar-nav">
+                        <li>
+                            <h4 class="navbar-text">Application Exception</h4>
+                        </li>
+                        <li class="divider-vertical"/>
+                        <li>
+                            <p class="navbar-text">
+                                Tapestry Version: ${tapestryVersion}
+                            </p>
+                        </li>
+                        <li class="divider-vertical"/>
+                        <li>
+                            <p class="navbar-text">
+                                Application Version: ${applicationVersion}
+                            </p>
+                        </li>
+                    </ul>
+                </div>
+            </div>
         </div>
-    </div>
-</div>
 
-<div class="container">
+        <div class="container">
 
-    <div class="alert alert-danger">
-        <h2>An exception has occurred processing this request.</h2>
+            <div class="panel panel-danger">
+                <div class="panel-heading">
+                    <h2 class="panel-title">An exception has occurred processing this request.</h2>
+                </div>
+                <div class="panel-body">
+                    ${rootException.message}
+                </div>
+                <t:if test="! productionMode">
 
-        <p>${rootException.message}</p>
-    </div>
+                    <div class="panel-footer">
 
-    <t:if test="! productionMode">
+                        <div class="btn-toolbar col-md-12">
 
-        <t:exceptiondisplay exception="rootException"/>
+                            <t:if test="failurePage">
+                                <t:pagelink page="prop:failurePage" class="btn btn-default">Go to page
+                                    <strong>${failurePage}</strong>
+                                </t:pagelink>
+                                <t:actionlink t:id="reloadFirst" parameters="{'loadPage': failurePage}"
+                                              context="reloadContext" class="btn btn-default">Go to page
+                                    <strong>${failurePage}</strong>
+                                    (with reload)
+                                </t:actionlink>
+                            </t:if>
+                            <a href="/" class="btn btn-default">Go to
+                                <strong>${rootURL}</strong>
+                            </a>
+                            <t:actionLink t:id="reloadRoot" class="btn btn-default">
+                                Go to
+                                <strong>${rootURL}</strong>
+                                (with reload)
+                            </t:actionLink>
+                        </div>
+                    </div>
+                </t:if>
+            </div>
 
-        <h3>Request</h3>
-        <t:renderobject object="request"/>
+            <t:if test="! productionMode">
 
-        <t:if test="hasSession">
-            <h2>Session</h2>
-            <dl>
-                <t:loop source="session.attributeNames" value="attributeName">
-                    <dt>${attributeName}</dt>
-                    <dd>
-                        <t:renderobject object="attributeValue"/>
-                    </dd>
-                </t:loop>
-            </dl>
-        </t:if>
+                <p class="help-text col-md-12"><strong>with reload</strong>: Force a reload of component
+                    classes. This is
+                    often
+                    necessary
+                    after fixing a class that previously failed to compile due to errors.
+                </p>
 
-        <h3>System Properties</h3>
-        <dl>
-            <t:loop source="systemProperties" value="propertyName">
-                <dt>${propertyName}</dt>
-                <dd>
-                    <t:if test="! complexProperty">
-                        ${propertyValue}
-                        <p:else>
-                            <ul>
-                                <li t:type="loop" source="complexPropertyValue" value="var:path">
-                                    ${var:path}
-                                </li>
-                            </ul>
-                        </p:else>
-                    </t:if>
-                </dd>
-            </t:loop>
-        </dl>
-    </t:if>
-</div>
-</body>
+                <t:exceptiondisplay exception="rootException"/>
+
+                <h3>Request</h3>
+                <t:renderobject object="request"/>
+
+                <t:if test="hasSession">
+                    <h2>Session</h2>
+                    <dl>
+                        <t:loop source="session.attributeNames" value="attributeName">
+                            <dt>${attributeName}</dt>
+                            <dd>
+                                <t:renderobject object="attributeValue"/>
+                            </dd>
+                        </t:loop>
+                    </dl>
+                </t:if>
+
+                <h3>System Properties</h3>
+                <dl>
+                    <t:loop source="systemProperties" value="propertyName">
+                        <dt>${propertyName}</dt>
+                        <dd>
+                            <t:if test="! complexProperty">
+                                ${propertyValue}
+                                <p:else>
+                                    <ul>
+                                        <li t:type="loop" source="complexPropertyValue" value="var:path">
+                                            ${var:path}
+                                        </li>
+                                    </ul>
+                                </p:else>
+                            </t:if>
+                        </dd>
+                    </t:loop>
+                </dl>
+            </t:if>
+        </div>
+    </body>
 
 </html>

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/7419eed3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
----------------------------------------------------------------------
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 65651ff..5e7c7a2 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
@@ -19,8 +19,10 @@
                 ${page.selector.toShortString()}
             </p:selectorCell>
             <p:empty>
-                <p><em>There are no pages in the page cache. This can only occur immediately after reloading all
-                    component classes or clearing the cache.</em></p>
+                <p>
+                    <em>There are no pages in the page cache. This can only occur immediately after clearing the cache.
+                    </em>
+                </p>
             </p:empty>
         </t:grid>
 
@@ -30,8 +32,7 @@
     <div class="btn-group">
         <t:actionlink t:id="forceLoad" zone="pages" class="btn btn-default">Load all pages</t:actionlink>
         <t:if test="! productionMode">
-            <t:actionlink t:id="clearCache" zone="pages" class="btn btn-default">Clear the cache</t:actionlink>
-            <t:actionlink t:id="reloadClasses" zone="pages" class="btn btn-default">Reload component classes
+            <t:actionlink t:id="clearCaches" zone="pages" class="btn btn-default">Clear Caches
             </t:actionlink>
         </t:if>
         <t:actionlink t:id="runGC" zone="pages" class="btn btn-default">Run the GC</t:actionlink>
@@ -59,7 +60,8 @@
 
     <div class="panel panel-default">
         <div class="panel-heading">
-            <h3 class="panel-title">Key</h3></div>
+            <h3 class="panel-title">Key</h3>
+        </div>
         <div class="panel-body">
 
             <dl class="dl-horizontal">