You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2011/09/29 18:34:36 UTC

svn commit: r1177356 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/corelib/pages/ main/java/org/apache/tapestry5/internal/pageload/ main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/inte...

Author: hlship
Date: Thu Sep 29 16:34:36 2011
New Revision: 1177356

URL: http://svn.apache.org/viewvc?rev=1177356&view=rev
Log:
TAP5-1678: Implement core/PageCatalog page
Add support for tracking page construction time and number of components
Track number of times a Page has been activated

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/
    tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/PageCatalogTests.groovy
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java?rev=1177356&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PageCatalog.java Thu Sep 29 16:34:36 2011
@@ -0,0 +1,94 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.corelib.pages;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.alerts.AlertManager;
+import org.apache.tapestry5.annotations.ContentType;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.internal.services.PageSource;
+import org.apache.tapestry5.internal.structure.Page;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
+
+import java.util.Collection;
+
+/**
+ * Lists out the currently loaded pages, using a {@link Grid}.  Provides an option to force all pages to be loaded. In development mode,
+ * includes an option to clear the page cache.
+ */
+@ContentType("text/html")
+public class PageCatalog
+{
+    @Property
+    @Inject
+    @Symbol(SymbolConstants.PRODUCTION_MODE)
+    private boolean productionMode;
+
+    @Inject
+    private PageSource pageSource;
+
+    @Inject
+    private ComponentResourceSelector selector;
+
+    @Inject
+    private ComponentClassResolver resolver;
+
+    @Inject
+    private AlertManager alertManager;
+
+    @Property
+    private Page page;
+
+    public Collection<Page> getPages()
+    {
+        return pageSource.getAllPages();
+    }
+
+    void onActionFromForceLoad()
+    {
+
+        int startCount = getPages().size();
+
+        for (String name : resolver.getPageNames())
+        {
+            pageSource.getPage(name);
+        }
+
+        int added = getPages().size() - startCount;
+
+        alertManager.info(String.format("Loaded %,d new pages for selector '%s'.", added, selector.toShortString()));
+    }
+
+    void onActionFromClearCache()
+    {
+        if (productionMode)
+        {
+            alertManager.error("Clearing the cache is not allowed in production mode");
+            return;
+        }
+
+        pageSource.clearCache();
+
+        alertManager.info("Page cache cleared.");
+    }
+
+    public String formatElapsed(long millis)
+    {
+        return String.format("%,d ms", millis);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java Thu Sep 29 16:34:36 2011
@@ -14,17 +14,10 @@
 
 package org.apache.tapestry5.internal.pageload;
 
-import java.util.List;
-import java.util.Map;
-
 import org.apache.tapestry5.Binding;
 import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
 import org.apache.tapestry5.internal.services.Instantiator;
-import org.apache.tapestry5.internal.structure.BodyPageElement;
-import org.apache.tapestry5.internal.structure.ComponentPageElement;
-import org.apache.tapestry5.internal.structure.ComponentPageElementImpl;
-import org.apache.tapestry5.internal.structure.ComponentPageElementResources;
-import org.apache.tapestry5.internal.structure.Page;
+import org.apache.tapestry5.internal.structure.*;
 import org.apache.tapestry5.ioc.Invokable;
 import org.apache.tapestry5.ioc.Location;
 import org.apache.tapestry5.ioc.OperationTracker;
@@ -41,6 +34,9 @@ import org.apache.tapestry5.services.Com
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
+import java.util.List;
+import java.util.Map;
+
 class ComponentAssemblerImpl implements ComponentAssembler
 {
     private final ComponentAssemblerSource assemblerSource;
@@ -68,9 +64,9 @@ class ComponentAssemblerImpl implements 
     private Map<String, EmbeddedComponentAssembler> embeddedIdToAssembler;
 
     public ComponentAssemblerImpl(ComponentAssemblerSource assemblerSource,
-            ComponentInstantiatorSource instantiatorSource, ComponentClassResolver componentClassResolver,
-            Instantiator instantiator, ComponentPageElementResources resources, OperationTracker tracker,
-            Request request, SymbolSource symbolSource)
+                                  ComponentInstantiatorSource instantiatorSource, ComponentClassResolver componentClassResolver,
+                                  Instantiator instantiator, ComponentPageElementResources resources, OperationTracker tracker,
+                                  Request request, SymbolSource symbolSource)
     {
         this.assemblerSource = assemblerSource;
         this.instantiatorSource = instantiatorSource;
@@ -98,8 +94,12 @@ class ComponentAssemblerImpl implements 
     {
         PageAssembly pageAssembly = new PageAssembly(page);
 
+        long startTime = System.currentTimeMillis();
+
         try
         {
+            pageAssembly.componentCount++;
+
             ComponentPageElement newElement = new ComponentPageElementImpl(pageAssembly.page, instantiator, resources,
                     request, symbolSource);
 
@@ -126,9 +126,10 @@ class ComponentAssemblerImpl implements 
                 action.execute(pageAssembly);
             }
 
+            page.setStats(System.currentTimeMillis() - startTime, pageAssembly.componentCount);
+
             return pageAssembly.createdElement.peek();
-        }
-        catch (RuntimeException ex)
+        } catch (RuntimeException ex)
         {
             throw new RuntimeException(PageloadMessages.exceptionAssemblingRootComponent(pageAssembly.page.getName(),
                     InternalUtils.toMessage(ex)), ex);
@@ -147,8 +148,8 @@ class ComponentAssemblerImpl implements 
     }
 
     public void assembleEmbeddedComponent(final PageAssembly pageAssembly,
-            final EmbeddedComponentAssembler embeddedAssembler, final String embeddedId, final String elementName,
-            final Location location)
+                                          final EmbeddedComponentAssembler embeddedAssembler, final String embeddedId, final String elementName,
+                                          final Location location)
     {
         ComponentName containerName = pageAssembly.componentName.peek();
 
@@ -172,6 +173,8 @@ class ComponentAssemblerImpl implements 
                     ComponentPageElement newElement = container.newChild(embeddedId, embeddedName.nestedId,
                             embeddedName.completeId, elementName, instantiator, location);
 
+                    pageAssembly.componentCount++;
+
                     pushNewElement(pageAssembly, newElement);
 
                     embeddedAssembler.addMixinsToElement(newElement);
@@ -181,8 +184,7 @@ class ComponentAssemblerImpl implements 
                     popNewElement(pageAssembly);
 
                     pageAssembly.componentName.pop();
-                }
-                catch (RuntimeException ex)
+                } catch (RuntimeException ex)
                 {
                     throw new TapestryException(PageloadMessages.exceptionAssemblingEmbeddedComponent(embeddedId,
                             componentClassName, container.getCompleteId(), InternalUtils.toMessage(ex)), location, ex);
@@ -274,13 +276,16 @@ class ComponentAssemblerImpl implements 
     }
 
     public EmbeddedComponentAssembler createEmbeddedAssembler(String embeddedId, String componentClassName,
-            EmbeddedComponentModel embeddedModel, String mixins, Location location)
+                                                              EmbeddedComponentModel embeddedModel, String mixins, Location location)
     {
         try
         {
 
-            if (InternalUtils.isBlank(componentClassName)) { throw new TapestryException(
-                    PageloadMessages.missingComponentType(), location, null); }
+            if (InternalUtils.isBlank(componentClassName))
+            {
+                throw new TapestryException(
+                        PageloadMessages.missingComponentType(), location, null);
+            }
             EmbeddedComponentAssemblerImpl embedded = new EmbeddedComponentAssemblerImpl(assemblerSource,
                     instantiatorSource, componentClassResolver, componentClassName, getSelector(), embeddedModel,
                     mixins, location);
@@ -299,9 +304,12 @@ class ComponentAssemblerImpl implements 
 
                     String existingEmbeddedId = publishedParameterToEmbeddedId.get(publishedParameterName);
 
-                    if (existingEmbeddedId != null) { throw new TapestryException(
-                            PageloadMessages.parameterAlreadyPublished(publishedParameterName, embeddedId, instantiator
-                                    .getModel().getComponentClassName(), existingEmbeddedId), location, null); }
+                    if (existingEmbeddedId != null)
+                    {
+                        throw new TapestryException(
+                                PageloadMessages.parameterAlreadyPublished(publishedParameterName, embeddedId, instantiator
+                                        .getModel().getComponentClassName(), existingEmbeddedId), location, null);
+                    }
 
                     publishedParameterToEmbeddedId.put(publishedParameterName, embeddedId);
                 }
@@ -309,8 +317,7 @@ class ComponentAssemblerImpl implements 
             }
 
             return embedded;
-        }
-        catch (Exception ex)
+        } catch (Exception ex)
         {
             throw new TapestryException(PageloadMessages.failureCreatingEmbeddedComponent(embeddedId, instantiator
                     .getModel().getComponentClassName(), InternalUtils.toMessage(ex)), location, ex);
@@ -333,20 +340,23 @@ class ComponentAssemblerImpl implements 
         // The complex case: a re-publish! Yes you can go deep here if you don't
         // value your sanity!
 
-        if (embeddedBinder != null) { return new ParameterBinder()
+        if (embeddedBinder != null)
         {
-            public void bind(ComponentPageElement element, Binding binding)
+            return new ParameterBinder()
             {
-                ComponentPageElement subelement = element.getEmbeddedElement(embeddedId);
+                public void bind(ComponentPageElement element, Binding binding)
+                {
+                    ComponentPageElement subelement = element.getEmbeddedElement(embeddedId);
 
-                embeddedBinder.bind(subelement, binding);
-            }
+                    embeddedBinder.bind(subelement, binding);
+                }
 
-            public String getDefaultBindingPrefix(String metaDefault)
-            {
-                return embeddedBinder.getDefaultBindingPrefix(metaDefault);
-            }
-        }; }
+                public String getDefaultBindingPrefix(String metaDefault)
+                {
+                    return embeddedBinder.getDefaultBindingPrefix(metaDefault);
+                }
+            };
+        }
 
         final ParameterBinder innerBinder = embededdedComponentAssembler.createParameterBinder(parameterName);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java Thu Sep 29 16:34:36 2011
@@ -48,6 +48,11 @@ class PageAssembly
 
     private final Set<String> flags = CollectionFactory.newSet();
 
+    /**
+     * Number of components constructed for this page instance.
+     */
+    int componentCount;
+
     PageAssembly(Page page)
     {
         this.page = page;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSource.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSource.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSource.java Thu Sep 29 16:34:36 2011
@@ -14,18 +14,18 @@
 
 package org.apache.tapestry5.internal.services;
 
-import java.util.Locale;
-
 import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.services.dynamic.DynamicTemplate;
 import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
+import java.util.Set;
+
 /**
  * Access to localized page instances (which are now shared singletons, starting in release 5.2).
  * This service is a wrapper around the {@link PageLoader} that caches the loaded pages.
- * 
+ *
  * @since 5.2.0
  */
 public interface PageSource
@@ -34,7 +34,7 @@ public interface PageSource
      * Clears the source's cache of loaded pages. This occurs when an outside change to the world invalidates
      * created page instances. Introduced to handle the case where a page has a {@link DynamicTemplate}, but the
      * underlying {@link Resource} is noticed to have changed.
-     * 
+     *
      * @since 5.3
      */
     void clearCache();
@@ -42,9 +42,19 @@ public interface PageSource
     /**
      * Returns a loaded instance of the indicated page, using the Locale and other information
      * from the {@link ComponentResourceSelector} obtained from the {@link ComponentRequestSelectorAnalyzer}.
-     * 
+     *
      * @param canonicalPageName
      * @return existing, or newly created, page instance
      */
     Page getPage(String canonicalPageName);
+
+    /**
+     * Returns all currently loaded pages. This will include any previously loaded pages not yet reclaimed by the
+     * garbage collector, and may include the same page loaded for different {@link ComponentResourceSelector}s. This is needed
+     * for reporting purposes only.
+     *
+     * @see org.apache.tapestry5.corelib.pages.PageCatalog
+     * @since 5.3
+     */
+    Set<Page> getAllPages();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java Thu Sep 29 16:34:36 2011
@@ -14,6 +14,8 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Mapper;
 import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.services.InvalidationListener;
@@ -22,6 +24,7 @@ import org.apache.tapestry5.services.pag
 
 import java.lang.ref.SoftReference;
 import java.util.Map;
+import java.util.Set;
 
 public class PageSourceImpl implements PageSource, InvalidationListener
 {
@@ -110,4 +113,15 @@ public class PageSourceImpl implements P
     {
         pageCache.clear();
     }
+
+    public Set<Page> getAllPages()
+    {
+        return F.flow(pageCache.values()).map(new Mapper<SoftReference<Page>, Page>()
+        {
+            public Page map(SoftReference<Page> element)
+            {
+                return element.get();
+            }
+        }).removeNulls().toSet();
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java Thu Sep 29 16:34:36 2011
@@ -182,4 +182,37 @@ public interface Page
      * Invoked to notify {@link PageResetListener} listeners.
      */
     void pageReset();
+
+
+    /**
+     * Invoked once at the end of page construction, to provide page construction statistics.
+     *
+     * @since 5.3
+     */
+    void setStats(long assemblyTime, int componentCount);
+
+    /**
+     * Returns the time, in milliseconds, to construct the page.
+     *
+     * @since 5.3
+     */
+    long getAssemblyTime();
+
+    /**
+     * Returns the number of components on the page, including the root component. This is a rough
+     * measure of complexity.
+     *
+     * @since 5.3
+     */
+    int getComponentCount();
+
+    /**
+     * Returns the number of times the page has been attached to a request. This is a rough measure
+     * of how important the page is, relative to other pages. This value is volatile, changing constantly.
+     *
+     * @since 5.3
+     */
+    int getAttachCount();
+
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java?rev=1177356&r1=1177355&r2=1177356&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java Thu Sep 29 16:34:36 2011
@@ -28,6 +28,7 @@ import org.slf4j.Logger;
 
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Pattern;
 
 public class PageImpl implements Page
@@ -50,6 +51,15 @@ public class PageImpl implements Page
 
     private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newCaseInsensitiveMap();
 
+    // TODO: loadComplete, assemblyTime, and componentCount are each set once without thread semantics,
+    // but before the instance is published to other threads ... is that enough?
+
+    private long assemblyTime;
+
+    private int componentCount;
+
+    private final AtomicInteger attachCount = new AtomicInteger();
+
     /**
      * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when
      * first needed,
@@ -75,6 +85,12 @@ public class PageImpl implements Page
         fieldBundle = perThreadManager.createValue();
     }
 
+    public void setStats(long assemblyTime, int componentCount)
+    {
+        this.assemblyTime = assemblyTime;
+        this.componentCount = componentCount;
+    }
+
     @Override
     public String toString()
     {
@@ -174,6 +190,8 @@ public class PageImpl implements Page
 
     public void attached()
     {
+        attachCount.incrementAndGet();
+
         for (PageLifecycleListener listener : lifecycleListeners)
             listener.restoreStateBeforePageAttach();
 
@@ -233,4 +251,18 @@ public class PageImpl implements Page
         return !resetListeners.isEmpty();
     }
 
+    public long getAssemblyTime()
+    {
+        return assemblyTime;
+    }
+
+    public int getComponentCount()
+    {
+        return componentCount;
+    }
+
+    public int getAttachCount()
+    {
+        return attachCount.get();
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml?rev=1177356&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/PageCatalog.tml Thu Sep 29 16:34:36 2011
@@ -0,0 +1,44 @@
+<html xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
+<head>
+    <title>Tapestry Page Catalog</title>
+</head>
+<body>
+
+<h1>Tapestry Page Catalog</h1>
+
+<t:alerts/>
+
+<p>
+    This page provides a list of pages currently loaded in the application.
+</p>
+
+<p>Actions:</p>
+<ul>
+    <li>
+        <t:pagelink page="pagecatalog">refresh the page</t:pagelink>
+    </li>
+    <li>
+        <t:actionlink t:id="forceLoad">load all pages</t:actionlink>
+    </li>
+    <t:if test="! productionMode">
+        <li>
+            <t:actionlink t:id="clearCache">clear the cache</t:actionlink>
+        </li>
+    </t:if>
+</ul>
+
+<t:grid source="pages" row="page" add="selector" reorder="name,selector">
+    <p:nameCell>
+        <t:pagelink page="prop:page.name">${page.name}</t:pagelink>
+    </p:nameCell>
+    <p:assemblyTimeCell>
+        ${formatElapsed(page.assemblyTime)}
+    </p:assemblyTimeCell>
+    <p:selectorCell>
+        ${page.selector.toShortString()}
+    </p:selectorCell>
+</t:grid>
+
+</body>
+
+</html>
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/PageCatalogTests.groovy
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/PageCatalogTests.groovy?rev=1177356&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/PageCatalogTests.groovy (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app3/PageCatalogTests.groovy Thu Sep 29 16:34:36 2011
@@ -0,0 +1,27 @@
+package org.apache.tapestry5.integration.app3
+
+import org.apache.tapestry5.test.SeleniumTestCase
+import org.testng.annotations.Test
+
+class PageCatalogTests extends SeleniumTestCase
+{
+
+    @Test
+    void load_page_catalog_page()
+    {
+
+        open("${baseURL}pagecatalog")
+
+        assertTitle "Tapestry Page Catalog"
+
+        clickAndWait "link=load all pages"
+
+        assertTitle "Tapestry Page Catalog"
+
+        clickAndWait "link=clear the cache"
+
+        assertTitle "Tapestry Page Catalog"
+
+        assertTextPresent "Page cache cleared"
+    }
+}