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 2008/01/13 02:48:34 UTC

svn commit: r611522 - in /tapestry/tapestry5/trunk: ./ tapestry-core/src/main/java/org/apache/tapestry/internal/services/ tapestry-core/src/main/java/org/apache/tapestry/services/ tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ ...

Author: hlship
Date: Sat Jan 12 17:48:32 2008
New Revision: 611522

URL: http://svn.apache.org/viewvc?rev=611522&view=rev
Log:
TAPESTRY-2006: Replace naive page pool mechanism with a more realistic one that can handle larger sites

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/CachedPage.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolCache.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolCacheTest.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePool.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/conf.apt
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/lifecycle.apt
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolImplTest.java
    tapestry/tapestry5/trunk/tapestry5.ipr

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/CachedPage.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/CachedPage.java?rev=611522&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/CachedPage.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/CachedPage.java Sat Jan 12 17:48:32 2008
@@ -0,0 +1,60 @@
+// Copyright 2008 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.tapestry.internal.services;
+
+import org.apache.tapestry.internal.structure.Page;
+
+import java.lang.ref.SoftReference;
+
+/**
+ * Tracks the usage of a page instance using a soft reference.  The soft reference will be reclaimed whenever the
+ * garbage collector requires it.
+ */
+public class CachedPage
+{
+    private final SoftReference<Page> _ref;
+
+    private long _lastAccess;
+
+    CachedPage(Page page)
+    {
+        _ref = new SoftReference<Page>(page);
+    }
+
+    /**
+     * Returns the page, or null if the reference has been reclaimed by the garbage collector.
+     *
+     * @return the page or null
+     */
+    Page get()
+    {
+        return _ref.get();
+    }
+
+    /**
+     * Time when the page was last returned to the available list.
+     *
+     * @return last access time (in milliseconds from the epoch)
+     */
+    long getLastAccess()
+    {
+        return _lastAccess;
+    }
+
+    void setLastAccess(long lastAccess)
+    {
+        _lastAccess = lastAccess;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePool.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePool.java?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePool.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePool.java Sat Jan 12 17:48:32 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 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.
@@ -22,11 +22,15 @@
 public interface PagePool
 {
     /**
-     * Obtains a page instance from the pool via a logical page name. A page instance is created if
-     * no such page is currently available.
+     * Obtains a page instance from the pool via a logical page name. A page instance is created if no such page is
+     * currently available.  The page pool enforces limits on the number of page instances (for any page name / locale
+     * combination) and may wait for a page to become available rather than create a new instance. There's also a hard
+     * limit, at which point an exception is raised.
      *
      * @param logicalPageName logical name used to identify the page
      * @return a page instance
+     * @throws RuntimeException if the name is not valid, if the page cannot be loaded, or if an instance of the page
+     *                          can't be created.
      */
     Page checkout(String logicalPageName);
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolCache.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolCache.java?rev=611522&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolCache.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolCache.java Sat Jan 12 17:48:32 2008
@@ -0,0 +1,355 @@
+// Copyright 2008 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.tapestry.internal.services;
+
+import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Used by  {@link org.apache.tapestry.internal.services.PagePoolImpl} to maintain a cache of available and in-use page
+ * instances.
+ * <p/>
+ * This code is designed to handle high volume sites and deal with request fluctuations.
+ * <p/>
+ * Page instances, once created, are tracked with <strong>soft</strong> references.
+ * <p/>
+ * A <em>soft limit</em> on the number of page instances is enforced. Requesting a page instance when the soft limit has
+ * been reached (or exceeded) will result in a delay until a page instance (released from another thread) is available.
+ * The delay time is configurable.
+ * <p/>
+ * A <em>hard limit</em> on the number of page instances is enforced. This number may not be exceeded. Requesting a page
+ * instance when at the hard limit will result in a runtime exception.
+ */
+final class PagePoolCache
+{
+    private final String _pageName;
+
+    private final Locale _locale;
+
+    private final int _softLimit;
+
+    private final long _softWait;
+
+    private final int _hardLimit;
+
+    private final long _activeWindow;
+
+    private final PageLoader _pageLoader;
+
+    /**
+     * Pages that are available for use.
+     */
+    private LinkedList<CachedPage> _available = CollectionFactory.newLinkedList();
+
+    /**
+     * Pages that are currently in use.
+     */
+    private LinkedList<CachedPage> _inUse = CollectionFactory.newLinkedList();
+
+    /**
+     * Guards access to the available and in use lists.
+     */
+    private final Lock _lock = new ReentrantLock();
+
+    /**
+     * Condition signalled whenever an in-use page is returned to the cache, which is useful if some other thread may be
+     * waiting for a page to be available.
+     */
+    private final Condition _pageAvailable = _lock.newCondition();
+
+    /**
+     * @param pageName     logical name of page, needed when creating a fresh instance
+     * @param locale       locale of the page, needed when creating a fresh instance
+     * @param pageLoader   used to create a fresh page instance, if necessary
+     * @param softLimit    soft limit on pages, point at which the cache will wait for an existing page to be made
+     *                     available
+     * @param softWait     interval, in milliseconds, to wait for a page to become available
+     * @param hardLimit    maximum number of page instances that will ever be created
+     * @param activeWindow interval, in milliseconds, beyond which an available page is simply discarded
+     */
+    public PagePoolCache(String pageName, Locale locale, PageLoader pageLoader, int softLimit, long softWait,
+                         int hardLimit, long activeWindow)
+    {
+        _pageName = pageName;
+        _locale = locale;
+        _pageLoader = pageLoader;
+        _softLimit = softLimit;
+        _softWait = softWait;
+        _hardLimit = hardLimit;
+        _activeWindow = activeWindow;
+    }
+
+    /**
+     * Finds an available page instance and returns it.  If no page instance is available, will wait up to the soft wait
+     * for one to become available. After that time, it will either create a new instance, or give up (the hard instance
+     * limit has been reached) and throw an exception.
+     *
+     * @return page instance
+     * @throws RuntimeException if the hard limit is reached, or if there is an error loading a new page instance
+     */
+    Page checkout()
+    {
+        // The only problem here is that *each* new page attached to the request
+        // may wait the soft limit.  The alternative would be to timestamp the request
+        // itself, and compute the wait period from that ... an dangerous and expensive option.
+
+        long start = System.currentTimeMillis();
+
+        // We don't set a wait on acquiring the lock; it is assumed that any given thread will
+        // only have the lock for an instant whether it is checking for an available page, or
+        // releasing pages from the in use list back into the active list. We go to some trouble to
+        // ensure that the PageLoader is invoked OUTSIDE of the lock.
+
+        _lock.lock();
+
+        try
+        {
+
+
+            while (true)
+            {
+                // We have the write lock, see if there is an available cached page we can use.
+
+                Page page = findAvailablePage();
+
+                if (page != null) return page;
+
+                // Now it starts to get tricky.  Have we hit the soft limit yet?
+                // We assume that none of the in use pages' soft references are cleared,
+                // which is largely accurate as long as there haven't been a lot
+                // of request exceptions.  We'll take the count at face value.
+
+                if (_inUse.size() < _softLimit) break;
+
+                // We'll wait for pages to be available, but careful that the
+                // total wait period is less than the soft wait limit.
+
+                long waitMillis = (start + _softWait) - System.currentTimeMillis();
+
+                // We've run out of time to wait.
+
+                if (waitMillis < 1) break;
+
+                try
+                {
+                    // Note: await() will release the lock, but will re-acquire it
+                    // before returning. 
+                    _pageAvailable.await(waitMillis, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException ex)
+                {
+                    // Not sure who is interrupting us (the servlet container)? But returning null
+                    // is the fastest way to bounce out of the thread.
+
+                    return null;
+                }
+
+                // Loop back up and see if an active page is available.  It won't always be,
+                // because of race conditions, so we may wait again.
+            }
+
+            // We get here if we exhausted the softWait interval without actually
+            // acquiring a page.            
+
+            // If past the hard limit, we don't try to create the page fresh.
+
+            if (_inUse.size() >= _hardLimit)
+                throw new RuntimeException(ServicesMessages.pagePoolExausted(_pageName, _locale, _hardLimit));
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+
+        // This may take a moment, so we're careful to do it outside of a write lock.
+        // That does mean that we may slip over a hard or soft limit momentarily, if
+        // just the right race condition occurs.
+
+        Page page = _pageLoader.loadPage(_pageName, _locale);
+
+        _lock.lock();
+
+        try
+        {
+            _inUse.addFirst(new CachedPage(page));
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+
+        return page;
+    }
+
+    /**
+     * Finds and returns the first available page.
+     * <p/>
+     * Side effect: removes the {@link org.apache.tapestry.internal.services.CachedPage} from the available list and
+     * moves it to the in use list.
+     *
+     * @return the page, if any found, or null if no page is available
+     */
+    private Page findAvailablePage()
+    {
+        CachedPage cachedPage = null;
+
+        ListIterator<CachedPage> i = _available.listIterator();
+
+        while (i.hasNext())
+        {
+            cachedPage = i.next();
+
+            // The CachePage should be removed, either because
+            // it is a valid page being moved to the in use list,
+            // or because it the soft reference has been reclaimed
+            // and it is no good to anyone.
+
+            i.remove();
+
+            Page page = cachedPage.get();
+
+            if (page != null)
+            {
+                _inUse.addFirst(cachedPage);
+
+                return page;
+            }
+
+        }
+
+        return null;
+    }
+
+    /**
+     * Invoked to release an active page back into the pool.
+     */
+    void release(Page page)
+    {
+        _lock.lock();
+
+        try
+        {
+            CachedPage cached = null;
+
+            ListIterator<CachedPage> i = _inUse.listIterator();
+
+            while (i.hasNext())
+            {
+                cached = i.next();
+
+                if (cached.get() == page)
+                {
+                    i.remove();
+                    break;
+                }
+
+            }
+
+            // This should not ever happen. The only scenario I can think of is if a page instance
+            // was in use before the page pool was cleared (due to a file check invalidation notification).
+            // That's not supposed to happen, CheckForUpdatesFilter ensures that all threads but one
+            // or blocked on the outside when a file check occurs.
+
+            // So, cached is null means that the page instance was not created by this
+            // PagePoolCache, so we're not interested in keeping it.
+
+            if (cached == null) return;
+
+            cached.setLastAccess(System.currentTimeMillis());
+
+            _available.addFirst(cached);
+
+            _pageAvailable.signal();
+
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    /**
+     * Called for dirty pages, pages that are in an unknown state after being used for the request. Such pages are
+     * removed from the in use list and NOT added back to the active list.
+     */
+    void remove(Page page)
+    {
+        _lock.lock();
+
+        try
+        {
+            ListIterator<CachedPage> i = _inUse.listIterator();
+
+            while (i.hasNext())
+            {
+                CachedPage cached = i.next();
+
+                if (cached.get() == page)
+                {
+                    i.remove();
+
+                    break;
+                }
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    /**
+     * Finds any cached pages whose last modified time is beyond the active window, meaning they haven't been used in
+     * some amount of time.  In addition, culls out any CachedPages that have been reclaimed by the garbage collector.
+     */
+    void cleanup()
+    {
+        long cutoff = System.currentTimeMillis() - _activeWindow;
+
+        _lock.lock();
+
+        try
+        {
+
+            ListIterator<CachedPage> i = _available.listIterator();
+
+            while (i.hasNext())
+            {
+                CachedPage cached = i.next();
+
+                if (cached.get() == null)
+                {
+                    i.remove();
+                    continue;
+                }
+
+                if (cached.getLastAccess() < cutoff) i.remove();
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolImpl.java?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PagePoolImpl.java Sat Jan 12 17:48:32 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,24 +15,41 @@
 package org.apache.tapestry.internal.services;
 
 import org.apache.tapestry.internal.events.InvalidationListener;
+import org.apache.tapestry.internal.events.UpdateListener;
 import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.ioc.annotations.IntermediateType;
+import org.apache.tapestry.ioc.annotations.Symbol;
 import org.apache.tapestry.ioc.internal.util.CollectionFactory;
-import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
 import org.apache.tapestry.ioc.services.ThreadLocale;
+import org.apache.tapestry.ioc.util.TimeInterval;
 import org.apache.tapestry.services.ComponentClassResolver;
 import org.slf4j.Logger;
 
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 /**
- * A very naive implementation just to get us past the start line.
+ * Registered as an invalidation listener with the page loader, the component messages source, and the component
+ * template source. Any time any of those notice a change, then the entire page pool is wiped. In addition, only soft
+ * references to pages are maintained, so when memory gets tight, page instances may be arbitrarily discarded.
  * <p/>
- * Registered as an invalidation listener with the
- * {@link org.apache.tapestry.internal.services.PageLoader}; thus the pool is cleared whenever
+ * The master page pool is, itself, divided into individual sub-pools, one for each combination of
+ * <p/>
+ * This code is designed to handle high volume sites and deal with request fluctuations.
+ * <p/>
+ * A <em>soft limit</em> on the number of page instances is enforced. Asking for a page instance when the soft limit has
+ * been reached (or exceeded) will result in a delay until a page instance (released from another thread) is available.
+ * The delay time is configurable.
+ * <p/>
+ * A <em>hard limit</em> on the number of page instances is enforced. This number may not be exceeded. Requesting a page
+ * instance when at the hard limit will result in a runtime exception.
+ * <p/>
+ * As an {@link org.apache.tapestry.internal.events.UpdateListener}, the service will reduce the size of each page's
+ * pool by eliminating pages that haven't been used recently.
+ *
+ * @see org.apache.tapestry.internal.services.PagePoolCache
  */
-public class PagePoolImpl implements PagePool, InvalidationListener
+public class PagePoolImpl implements PagePool, InvalidationListener, UpdateListener
 {
     private final Logger _logger;
 
@@ -42,62 +59,106 @@
 
     private final ComponentClassResolver _resolver;
 
-    private final Map<PageLocator, List<Page>> _pool = newMap();
+    private final int _softLimit;
+
+    private final long _softWait;
+
+    private final int _hardLimit;
+
+    private final long _activeWindow;
+
+    private final Map<PageLocator, PagePoolCache> _pool = CollectionFactory.newMap();
 
-    public PagePoolImpl(Logger logger, PageLoader pageLoader, ThreadLocale threadLocale,
-                        ComponentClassResolver resolver)
+    public PagePoolImpl(Logger logger,
+
+                        PageLoader pageLoader,
+
+                        ThreadLocale threadLocale,
+
+                        ComponentClassResolver resolver,
+
+                        @Symbol("tapestry.page-pool.soft-limit")
+                        int softLimit,
+
+                        @Symbol("tapestry.page-pool.soft-wait") @IntermediateType(TimeInterval.class)
+                        long softWait,
+
+                        @Symbol("tapestry.page-pool.hard-limit")
+                        int hardLimit,
+
+                        @Symbol("tapestry.page-pool.active-window") @IntermediateType(TimeInterval.class)
+                        long activeWindow)
     {
         _logger = logger;
         _pageLoader = pageLoader;
         _threadLocale = threadLocale;
         _resolver = resolver;
+        _softLimit = softLimit;
+        _softWait = softWait;
+        _hardLimit = hardLimit;
+        _activeWindow = activeWindow;
     }
 
-    public synchronized Page checkout(String logicalPageName)
+    public Page checkout(String logicalPageName)
     {
         String canonicalPageName = _resolver.canonicalizePageName(logicalPageName);
 
-        Locale locale = _threadLocale.getLocale();
-        List<Page> pages = _pool.get(new PageLocator(canonicalPageName, locale));
-
-        // When we load a page, we load it in the active locale, whatever that is.
-        // Even if the locale later changes, we keep the version we originally got.
-        // This is not as bad in T5 as in T4, since a seperate request will
-        // render the response (and will have a chance to get the page in a different locale).
+        PagePoolCache cache = get(canonicalPageName, _threadLocale.getLocale());
 
-        if (pages == null || pages.isEmpty())
-            return _pageLoader.loadPage(canonicalPageName, locale);
-
-        // Remove and return the last page in the pool.
-
-        return pages.remove(pages.size() - 1);
+        return cache.checkout();
     }
 
-    public synchronized void release(Page page)
+    public void release(Page page)
     {
-        boolean dirty = page.detached();
+        PagePoolCache cache = get(page.getLogicalName(), page.getLocale());
 
-        if (dirty)
+        // If the page is not "clean" of any request/client state, it can't go
+        // back in the pool.
+
+        if (page.detached())
         {
             _logger.error(ServicesMessages.pageIsDirty(page));
+
+            cache.remove(page);
+
             return;
         }
 
-        PageLocator locator = new PageLocator(page.getLogicalName(), page.getLocale());
-        List<Page> pages = _pool.get(locator);
+        cache.release(page);
+
+    }
+
+    private synchronized PagePoolCache get(String pageName, Locale locale)
+    {
+        PageLocator locator = new PageLocator(pageName, locale);
+
+        PagePoolCache result = _pool.get(locator);
 
-        if (pages == null)
+        if (result == null)
         {
-            pages = CollectionFactory.newList();
-            _pool.put(locator, pages);
+            // TODO: It might be nice to allow individual pages to override the default limits.
+
+            result = new PagePoolCache(pageName, locale, _pageLoader, _softLimit, _softWait, _hardLimit, _activeWindow);
+
+            _pool.put(locator, result);
         }
 
-        pages.add(page);
+        return result;
     }
 
+    /**
+     * Any time templates, classes or messages change, we through out all instances.
+     */
     public synchronized void objectWasInvalidated()
     {
         _pool.clear();
     }
 
+    public synchronized void checkForUpdates()
+    {
+        for (PagePoolCache cache : _pool.values())
+        {
+            cache.cleanup();
+        }
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Sat Jan 12 17:48:32 2008
@@ -29,6 +29,7 @@
 import java.net.URL;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 
 class ServicesMessages
 {
@@ -394,5 +395,10 @@
     static String attributeNotAllowed(String elementName)
     {
         return MESSAGES.format("attribute-not-allowed", elementName);
+    }
+
+    static String pagePoolExausted(String pageName, Locale locale, int hardLimit)
+    {
+        return MESSAGES.format("page-pool-exausted", pageName, locale.toString(), hardLimit);
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java Sat Jan 12 17:48:32 2008
@@ -133,8 +133,8 @@
     }
 
     /**
-     * A companion service to {@linkplain #build(org.slf4j.Logger, String, AliasManager, java.util.Collection)}  the Alias service}
-     * whose configuration contribution define spot overrides to specific services.
+     * A companion service to {@linkplain #build(org.slf4j.Logger, String, AliasManager, java.util.Collection)}  the
+     * Alias service} whose configuration contribution define spot overrides to specific services.
      */
     public static AliasManager buildAliasOverrides(Logger logger, Collection<AliasContribution> configuration)
     {
@@ -142,8 +142,8 @@
     }
 
     /**
-     * Contributes the factory for serveral built-in binding prefixes ("asset", "literal", prop",
-     * "block", "component" "message", "validate", "translate", "var").
+     * Contributes the factory for serveral built-in binding prefixes ("asset", "literal", prop", "block", "component"
+     * "message", "validate", "translate", "var").
      */
     public static void contributeBindingSource(MappedConfiguration<String, BindingFactory> configuration,
 
@@ -190,33 +190,24 @@
     }
 
     /**
-     * Adds a number of standard component class transform workers:
-     * <ul>
-     * <li>Retain -- allows fields to retain their values between requests</li>
-     * <li>Persist -- allows fields to store their their value persistently between requests</li>
-     * <li>Parameter -- identifies parameters based on the
-     * {@link org.apache.tapestry.annotations.Parameter} annotation</li>
-     * <li>Component -- identifies embedded components based on the
-     * {@link org.apache.tapestry.annotations.Component} annotation</li>
-     * <li>Mixin -- adds a mixin as part of a component's implementation</li>
-     * <li>Environment -- allows fields to contain values extracted from the {@link Environment}
-     * service</li>
-     * <li>Inject -- used with the {@link Inject} annotation, when a value is supplied</li>
-     * <li>InjectPage -- adds code to allow access to other pages via the {@link InjectPage} field
-     * annotation</li>
-     * <li>InjectBlock -- allows a block from the template to be injected into a field</li>
-     * <li>IncludeStylesheet -- supports the {@link org.apache.tapestry.annotations.IncludeStylesheet} annotation</li>
-     * <li>IncludeJavaScriptLibrary -- supports the {@link org.apache.tapestry.annotations.IncludeJavaScriptLibrary} annotation</li>
-     * <li>SupportsInformalParameters -- checks for the annotation</li>
-     * <li>Meta -- checks for meta data and adds it to the component model
-     * <li>ApplicationState -- converts fields that reference application state objects
-     * <li>UnclaimedField -- identifies unclaimed fields and resets them to null/0/false at the end
-     * of the request</li>
-     * <li>RenderCommand -- ensures all components also implement {@link RenderCommand}</li>
-     * <li>SetupRender, BeginRender, etc. -- correspond to component render phases and annotations</li>
-     * <li>InvokePostRenderCleanupOnResources -- makes sure {@link org.apache.tapestry.internal.InternalComponentResources#postRenderCleanup()} is invoked
-     * after a component finishes rendering</li>
-     * </ul>
+     * Adds a number of standard component class transform workers: <ul> <li>Retain -- allows fields to retain their
+     * values between requests</li> <li>Persist -- allows fields to store their their value persistently between
+     * requests</li> <li>Parameter -- identifies parameters based on the {@link org.apache.tapestry.annotations.Parameter}
+     * annotation</li> <li>Component -- identifies embedded components based on the {@link
+     * org.apache.tapestry.annotations.Component} annotation</li> <li>Mixin -- adds a mixin as part of a component's
+     * implementation</li> <li>Environment -- allows fields to contain values extracted from the {@link Environment}
+     * service</li> <li>Inject -- used with the {@link Inject} annotation, when a value is supplied</li> <li>InjectPage
+     * -- adds code to allow access to other pages via the {@link InjectPage} field annotation</li> <li>InjectBlock --
+     * allows a block from the template to be injected into a field</li> <li>IncludeStylesheet -- supports the {@link
+     * org.apache.tapestry.annotations.IncludeStylesheet} annotation</li> <li>IncludeJavaScriptLibrary -- supports the
+     * {@link org.apache.tapestry.annotations.IncludeJavaScriptLibrary} annotation</li> <li>SupportsInformalParameters
+     * -- checks for the annotation</li> <li>Meta -- checks for meta data and adds it to the component model
+     * <li>ApplicationState -- converts fields that reference application state objects <li>UnclaimedField -- identifies
+     * unclaimed fields and resets them to null/0/false at the end of the request</li> <li>RenderCommand -- ensures all
+     * components also implement {@link RenderCommand}</li> <li>SetupRender, BeginRender, etc. -- correspond to
+     * component render phases and annotations</li> <li>InvokePostRenderCleanupOnResources -- makes sure {@link
+     * org.apache.tapestry.internal.InternalComponentResources#postRenderCleanup()} is invoked after a component
+     * finishes rendering</li> </ul>
      */
     public static void contributeComponentClassTransformWorker(
             OrderedConfiguration<ComponentClassTransformWorker> configuration,
@@ -298,12 +289,9 @@
     }
 
     /**
-     * <dl>
-     * <dt>Annotation</dt>
-     * <dd>Checks for {@link org.apache.tapestry.beaneditor.DataType} annotation</dd>
-     * <dt>Default  (ordered last)</dt>
-     * <dd>{@link org.apache.tapestry.internal.services.DefaultDataTypeAnalyzer} service ({@link #contributeDefaultDataTypeAnalyzer(org.apache.tapestry.ioc.MappedConfiguration)} })</dd>
-     * </dl>
+     * <dl> <dt>Annotation</dt> <dd>Checks for {@link org.apache.tapestry.beaneditor.DataType} annotation</dd>
+     * <dt>Default  (ordered last)</dt> <dd>{@link org.apache.tapestry.internal.services.DefaultDataTypeAnalyzer}
+     * service ({@link #contributeDefaultDataTypeAnalyzer(org.apache.tapestry.ioc.MappedConfiguration)} })</dd> </dl>
      */
     public static void contributeDataTypeAnalyzer(OrderedConfiguration<DataTypeAnalyzer> configuration,
                                                   @InjectService("DefaultDataTypeAnalyzer")
@@ -314,14 +302,8 @@
     }
 
     /**
-     * Maps property types to data type names:
-     * <ul>
-     * <li>String --&gt; text
-     * <li>Number --&gt; text
-     * <li>Enum --&gt; enum
-     * <li>Boolean --&gt; checkbox
-     * <li>Date --&gt; date
-     * </ul>
+     * Maps property types to data type names: <ul> <li>String --&gt; text <li>Number --&gt; text <li>Enum --&gt; enum
+     * <li>Boolean --&gt; checkbox <li>Date --&gt; date </ul>
      */
     public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class, String> configuration)
     {
@@ -385,15 +367,8 @@
     }
 
     /**
-     * Contributes the basic set of validators:
-     * <ul>
-     * <li>required</li>
-     * <li>minlength</li>
-     * <li>maxlength</li>
-     * <li>min</li>
-     * <li>max</li>
-     * <li>regexp</li>
-     * </ul>
+     * Contributes the basic set of validators: <ul> <li>required</li> <li>minlength</li> <li>maxlength</li>
+     * <li>min</li> <li>max</li> <li>regexp</li> </ul>
      */
     public static void contributeFieldValidatorSource(MappedConfiguration<String, Validator> configuration)
     {
@@ -406,17 +381,12 @@
     }
 
     /**
-     * Contributes the base set of injection providers:
-     * <dl>
-     * <dt>Default</dt> <dd>based on {@link MasterObjectProvider}</dd>
-     * <dt>Block</dt> <dd>injects fields of type Block</dd>
-     * <dt>ComponentResources</dt> <dd>give component access to its resources</dd>
-     * <dt>CommonResources</dt> <dd>access to properties of resources (log, messages, etc.)</dd>
-     * <dt>Asset</dt> <dd>injection of assets (triggered via {@link Path} annotation), with the path
-     * relative to the component class</dd>
-     * <dt>Service</dt> <dd>ordered last, for use when Inject is present and nothing else works, matches
-     * field type against Tapestry IoC services</dd>
-     * </dl>
+     * Contributes the base set of injection providers: <dl> <dt>Default</dt> <dd>based on {@link
+     * MasterObjectProvider}</dd> <dt>Block</dt> <dd>injects fields of type Block</dd> <dt>ComponentResources</dt>
+     * <dd>give component access to its resources</dd> <dt>CommonResources</dt> <dd>access to properties of resources
+     * (log, messages, etc.)</dd> <dt>Asset</dt> <dd>injection of assets (triggered via {@link Path} annotation), with
+     * the path relative to the component class</dd> <dt>Service</dt> <dd>ordered last, for use when Inject is present
+     * and nothing else works, matches field type against Tapestry IoC services</dd> </dl>
      */
     public static void contributeInjectionProvider(OrderedConfiguration<InjectionProvider> configuration,
 
@@ -447,13 +417,10 @@
     }
 
     /**
-     * Contributes two object providers:
-     * <dl>
-     * <dt>Alias</dt> <dd> Searches by type among {@linkplain AliasContribution contributions} to the
-     * {@link Alias} service</dd>
-     * <dt>Asset<dt> <dd> Checks for the {@link Path} annotation, and injects an {@link Asset}</dd>
-     * <dt>Service</dt> <dd>Injects based on the {@link Service} annotation, if present</dd>
-     * </dl>
+     * Contributes two object providers: <dl> <dt>Alias</dt> <dd> Searches by type among {@linkplain AliasContribution
+     * contributions} to the {@link Alias} service</dd> <dt>Asset<dt> <dd> Checks for the {@link Path} annotation, and
+     * injects an {@link Asset}</dd> <dt>Service</dt> <dd>Injects based on the {@link Service} annotation, if
+     * present</dd> </dl>
      */
     public static void contributeMasterObjectProvider(OrderedConfiguration<ObjectProvider> configuration,
 
@@ -483,15 +450,13 @@
     }
 
     /**
-     * Continues a number of filters into the RequestHandler service:
-     * <dl>
-     * <dt>StaticFiles</dt> <dd>Checks to see if the request is for an actual file, if so, returns true to let
-     * the servlet container process the request</dd>
-     * <dt>CheckForUpdates</dt> <dd>Periodically fires events that checks to see if the file system sources
-     * for any cached data has changed (see {@link org.apache.tapestry.internal.services.CheckForUpdatesFilter}).
-     * <dt>ErrorFilter</dt> <dd>Catches request errors and lets the {@link org.apache.tapestry.services.RequestExceptionHandler} handle them</dd>
-     * <dt>Localization</dt> <dd>Determines the locale for the current request from header data or cookies in the request</dd>
-     * </dl>
+     * Continues a number of filters into the RequestHandler service: <dl> <dt>StaticFiles</dt> <dd>Checks to see if the
+     * request is for an actual file, if so, returns true to let the servlet container process the request</dd>
+     * <dt>CheckForUpdates</dt> <dd>Periodically fires events that checks to see if the file system sources for any
+     * cached data has changed (see {@link org.apache.tapestry.internal.services.CheckForUpdatesFilter}).
+     * <dt>ErrorFilter</dt> <dd>Catches request errors and lets the {@link org.apache.tapestry.services.RequestExceptionHandler}
+     * handle them</dd> <dt>Localization</dt> <dd>Determines the locale for the current request from header data or
+     * cookies in the request</dd> </dl>
      */
     public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration, Context context,
 
@@ -546,13 +511,8 @@
 
 
     /**
-     * Contributes the basic set of default translators:
-     * <ul>
-     * <li>Integer</li>
-     * <li>String</li>
-     * <li>Long</li>
-     * <li>Double</li>
-     * </li>
+     * Contributes the basic set of default translators: <ul> <li>Integer</li> <li>String</li> <li>Long</li>
+     * <li>Double</li> </li>
      */
     public static void contributeTranslatorDefaultSource(MappedConfiguration<Class, Translator> configuration)
     {
@@ -563,13 +523,8 @@
     }
 
     /**
-     * Contributes the basic set of named translators:
-     * <ul>
-     * <li>integer</li>
-     * <li>string</li>
-     * <li>long</li>
-     * <li>double</li>
-     * </ul>
+     * Contributes the basic set of named translators: <ul> <li>integer</li> <li>string</li> <li>long</li>
+     * <li>double</li> </ul>
      */
     public static void contributeTranslatorSource(MappedConfiguration<String, Translator> configuration)
     {
@@ -585,16 +540,9 @@
     }
 
     /**
-     * Adds coercions:
-     * <ul>
-     * <li>String to {@link SelectModel}
-     * <li>Map to {@link SelectModel}
-     * <li>Collection to {@link GridDataSource}
-     * <li>null to {@link GridDataSource}
-     * <li>String to {@link GridPagerPosition}
-     * <li>List to {@link SelectModel}
-     * <li>{@link ComponentResourcesAware} (typically, a component) to {@link ComponentResources}
-     * </ul>
+     * Adds coercions: <ul> <li>String to {@link SelectModel} <li>Map to {@link SelectModel} <li>Collection to {@link
+     * GridDataSource} <li>null to {@link GridDataSource} <li>String to {@link GridPagerPosition} <li>List to {@link
+     * SelectModel} <li>{@link ComponentResourcesAware} (typically, a component) to {@link ComponentResources} </ul>
      */
     public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration)
     {
@@ -665,11 +613,8 @@
     }
 
     /**
-     * Adds built-in constraint generators:
-     * <ul>
-     * <li>PrimtiveField -- primitive fields are always required
-     * <li>ValidateAnnotation -- adds constraints from a {@link Validate} annotation
-     * </ul>
+     * Adds built-in constraint generators: <ul> <li>PrimtiveField -- primitive fields are always required
+     * <li>ValidateAnnotation -- adds constraints from a {@link Validate} annotation </ul>
      */
     public static void contributeValidationConstraintGenerator(
             OrderedConfiguration<ValidationConstraintGenerator> configuration)
@@ -817,9 +762,8 @@
     }
 
     /**
-     * Builds the source of {@link Messages} containing validation messages. The contributions are
-     * paths to message bundles (resource paths within the classpath); the default contribution is
-     * "org/apache/tapestry/internal/ValidationMessages".
+     * Builds the source of {@link Messages} containing validation messages. The contributions are paths to message
+     * bundles (resource paths within the classpath); the default contribution is "org/apache/tapestry/internal/ValidationMessages".
      */
     public ValidationMessagesSource build(Collection<String> configuration, UpdateListenerHub updateListenerHub,
 
@@ -843,7 +787,8 @@
     }
 
     /**
-     * Builds a proxy to the current {@link org.apache.tapestry.PageRenderSupport} inside this thread's {@link Environment}.
+     * Builds a proxy to the current {@link org.apache.tapestry.PageRenderSupport} inside this thread's {@link
+     * Environment}.
      */
     public PageRenderSupport buildPageRenderSupport(EnvironmentalShadowBuilder builder)
     {
@@ -851,7 +796,8 @@
     }
 
     /**
-     * Builds a proxy to the current {@link org.apache.tapestry.services.FormSupport} inside this thread's {@link org.apache.tapestry.services.Environment}.
+     * Builds a proxy to the current {@link org.apache.tapestry.services.FormSupport} inside this thread's {@link
+     * org.apache.tapestry.services.Environment}.
      */
     public FormSupport buildFormSupport(EnvironmentalShadowBuilder builder)
     {
@@ -867,10 +813,9 @@
     }
 
     /**
-     * Analyzes properties to determine the data types, used to
-     * {@linkplain #contributeBeanBlockSource(org.apache.tapestry.ioc.Configuration)} locale display and edit blocks}
-     * for properties.  The default behaviors look for a {@link org.apache.tapestry.beaneditor.DataType} annotation before
-     * deriving the data type from the property type.
+     * Analyzes properties to determine the data types, used to {@linkplain #contributeBeanBlockSource(org.apache.tapestry.ioc.Configuration)}
+     * locale display and edit blocks} for properties.  The default behaviors look for a {@link
+     * org.apache.tapestry.beaneditor.DataType} annotation before deriving the data type from the property type.
      */
     @Marker(Primary.class)
     public DataTypeAnalyzer build(List<DataTypeAnalyzer> configuration)
@@ -879,9 +824,9 @@
     }
 
     /**
-     * A chain of command for providing values for {@link Inject}-ed
-     * fields in component classes. The service's configuration can be extended to allow for
-     * different automatic injections (based on some combination of field type and field name).
+     * A chain of command for providing values for {@link Inject}-ed fields in component classes. The service's
+     * configuration can be extended to allow for different automatic injections (based on some combination of field
+     * type and field name).
      */
 
     public InjectionProvider build(List<InjectionProvider> configuration)
@@ -999,9 +944,8 @@
     }
 
     /**
-     * The default data type analyzer is the final analyzer consulted and identifies the type
-     * entirely pased on the property type, working against its own configuration (mapping property
-     * type class to data type).
+     * The default data type analyzer is the final analyzer consulted and identifies the type entirely pased on the
+     * property type, working against its own configuration (mapping property type class to data type).
      */
     public DataTypeAnalyzer buildDefaultDataTypeAnalyzer(ServiceResources resources)
     {
@@ -1046,11 +990,10 @@
     }
 
     /**
-     * Returns a {@link ClassFactory} that can be used to create extra classes around component
-     * classes. This ClassFactory will be cleared whenever an underlying component class is
-     * discovered to have changed. Use of this class factory implies that your code will become
-     * aware of this (if necessary) to discard any cached object (alas, this currently involves
-     * dipping into the internals side to register for the correct notifications). Failure to
+     * Returns a {@link ClassFactory} that can be used to create extra classes around component classes. This
+     * ClassFactory will be cleared whenever an underlying component class is discovered to have changed. Use of this
+     * class factory implies that your code will become aware of this (if necessary) to discard any cached object (alas,
+     * this currently involves dipping into the internals side to register for the correct notifications). Failure to
      * properly clean up can result in really nasty PermGen space memory leaks.
      */
     @Marker(ComponentLayer.class)
@@ -1061,8 +1004,7 @@
 
 
     /**
-     * Ordered contributions to the MasterDispatcher service allow different URL matching strategies
-     * to occur.
+     * Ordered contributions to the MasterDispatcher service allow different URL matching strategies to occur.
      */
     public Dispatcher buildMasterDispatcher(List<Dispatcher> configuration)
     {
@@ -1079,8 +1021,8 @@
     }
 
     /**
-     * Builds a shadow of the RequestGlobals.request property. Note again that the shadow can be an
-     * ordinary singleton, even though RequestGlobals is perthread.
+     * Builds a shadow of the RequestGlobals.request property. Note again that the shadow can be an ordinary singleton,
+     * even though RequestGlobals is perthread.
      */
     public Request buildRequest()
     {
@@ -1088,9 +1030,8 @@
     }
 
     /**
-     * Builds a shadow of the RequestGlobals.HTTPServletRequest property.  Generally, you should inject
-     * the {@link Request} service instead, as future version of Tapestry may operate beyond just the
-     * servlet API.
+     * Builds a shadow of the RequestGlobals.HTTPServletRequest property.  Generally, you should inject the {@link
+     * Request} service instead, as future version of Tapestry may operate beyond just the servlet API.
      */
     public HttpServletRequest buildHttpServletRequest()
     {
@@ -1098,8 +1039,8 @@
     }
 
     /**
-     * Builds a shadow of the RequestGlobals.response property. Note again that the shadow can be an
-     * ordinary singleton, even though RequestGlobals is perthread.
+     * Builds a shadow of the RequestGlobals.response property. Note again that the shadow can be an ordinary singleton,
+     * even though RequestGlobals is perthread.
      */
     public Response buildResponse()
     {
@@ -1127,25 +1068,14 @@
     }
 
     /**
-     * Contributes handlers for the following types:
-     * <dl>
-     * <dt>Object</dt>
-     * <dd>Failure case, added to provide a more useful exception message</dd>
-     * <dt>{@link Link}</dt>
-     * <dd>Sends a redirect to the link (which is typically a page render link)</dd>
-     * <dt>String</dt>
-     * <dd>Sends a page render redirect</dd>
-     * <dt>Class</dt>
-     * <dd>Interpreted as the class name of a page, sends a page render render redirect (this is more refactoring safe
-     * than the page name)</dd>
-     * <dt>{@link Component}</dt>
-     * <dd>A page's root component (though a non-root component will work, but will generate a warning). A direct
-     * to the containing page is sent.</dd>
-     * <dt>{@link org.apache.tapestry.StreamResponse}</dt>
-     * <dd>The stream response is sent as the actual reply.</dd>
-     * <dt>URL</dt>
-     * <dd>Sends a redirect to a (presumably) external URL</dd>
-     * </dl>
+     * Contributes handlers for the following types: <dl> <dt>Object</dt> <dd>Failure case, added to provide a more
+     * useful exception message</dd> <dt>{@link Link}</dt> <dd>Sends a redirect to the link (which is typically a page
+     * render link)</dd> <dt>String</dt> <dd>Sends a page render redirect</dd> <dt>Class</dt> <dd>Interpreted as the
+     * class name of a page, sends a page render render redirect (this is more refactoring safe than the page name)</dd>
+     * <dt>{@link Component}</dt> <dd>A page's root component (though a non-root component will work, but will generate
+     * a warning). A direct to the containing page is sent.</dd> <dt>{@link org.apache.tapestry.StreamResponse}</dt>
+     * <dd>The stream response is sent as the actual reply.</dd> <dt>URL</dt> <dd>Sends a redirect to a (presumably)
+     * external URL</dd> </dl>
      */
     public void contributeComponentEventResultProcessor(
 
@@ -1186,15 +1116,11 @@
 
 
     /**
-     * Contributes handlers for the following types:
-     * <dl>
-     * <dt>Object</dt> <dd>Failure case, added to provide more useful exception message</dd>
-     * <dt>{@link RenderCommand}</dt> <dd>Typically, a {@link org.apache.tapestry.Block}</dd>
-     * <dt>{@link Component}</dt> <dd>Renders the component and its body</dd>
-     * <dt>{@link org.apache.tapestry.json.JSONObject}</dt>
-     * <dd>The JSONObject is returned as a text/javascript response</dd>
-     * <dt>{@link org.apache.tapestry.StreamResponse}</dt>
-     * <dd>The stream response is sent as the actual response</dd>
+     * Contributes handlers for the following types: <dl> <dt>Object</dt> <dd>Failure case, added to provide more useful
+     * exception message</dd> <dt>{@link RenderCommand}</dt> <dd>Typically, a {@link org.apache.tapestry.Block}</dd>
+     * <dt>{@link Component}</dt> <dd>Renders the component and its body</dd> <dt>{@link
+     * org.apache.tapestry.json.JSONObject}</dt> <dd>The JSONObject is returned as a text/javascript response</dd>
+     * <dt>{@link org.apache.tapestry.StreamResponse}</dt> <dd>The stream response is sent as the actual response</dd>
      * </dl>
      */
 
@@ -1243,15 +1169,9 @@
     }
 
     /**
-     * Contributes meta data defaults:
-     * <dl>
-     * <dt>{@link PersistentFieldManagerImpl#META_KEY}
-     * <dd>{@link PersistentFieldManagerImpl#DEFAULT_STRATEGY}
-     * <dt>{@link TapestryConstants#RESPONSE_CONTENT_TYPE}
-     * <dd>text/html
-     * <dt>{@link TapestryConstants#RESPONSE_ENCODING}
-     * <dd>UTF-8
-     * </dl>
+     * Contributes meta data defaults: <dl> <dt>{@link PersistentFieldManagerImpl#META_KEY} <dd>{@link
+     * PersistentFieldManagerImpl#DEFAULT_STRATEGY} <dt>{@link TapestryConstants#RESPONSE_CONTENT_TYPE} <dd>text/html
+     * <dt>{@link TapestryConstants#RESPONSE_ENCODING} <dd>UTF-8 </dl>
      *
      * @param configuration
      */
@@ -1264,8 +1184,8 @@
     }
 
     /**
-     * Contributes a default object renderer for type Object, plus specialized renderers for
-     * {@link Request} and {@link Location}.
+     * Contributes a default object renderer for type Object, plus specialized renderers for {@link Request} and {@link
+     * Location}.
      */
     public void contributeObjectRenderer(MappedConfiguration<Class, ObjectRenderer> configuration,
 
@@ -1301,14 +1221,16 @@
 
 
     /**
-     * The MarkupRenderer service is used to render a full page as markup.  Supports an ordered
-     * configuration of {@link org.apache.tapestry.services.MarkupRendererFilter}s.
+     * The MarkupRenderer service is used to render a full page as markup.  Supports an ordered configuration of {@link
+     * org.apache.tapestry.services.MarkupRendererFilter}s.
      *
      * @param pageRenderQueue handles the bulk of the work
      * @param logger          used to log errors building the pipeline
      * @param configuration   filters on this service
      * @return the service
-     * @see #contributeMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset, org.apache.tapestry.Asset, ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource, AssetSource)
+     * @see #contributeMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset,
+     *      org.apache.tapestry.Asset, ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource,
+     *      AssetSource)
      */
     public MarkupRenderer buildMarkupRenderer(final PageRenderQueue pageRenderQueue, Logger logger,
                                               List<MarkupRendererFilter> configuration)
@@ -1327,15 +1249,12 @@
 
 
     /**
-     * Adds page render filters, each of which provides an {@link org.apache.tapestry.annotations.Environmental} service.  Filters often
-     * provide {@link Environmental} services needed by components as they render.
-     * <dl>
-     * <dt>PageRenderSupport</dt>  <dd>Provides {@link PageRenderSupport}</dd>
-     * <dt>ZoneSetup</dt> <dd>Provides {@link ZoneSetup}</dd>
-     * <dt>Heartbeat</dt> <dd>Provides {@link org.apache.tapestry.services.Heartbeat}</dd>
-     * <dt>DefaultValidationDecorator</dt>
-     * <dd>Provides {@link org.apache.tapestry.ValidationDecorator} (as an instance of {@link org.apache.tapestry.internal.DefaultValidationDecorator})</dd>
-     * </dl>
+     * Adds page render filters, each of which provides an {@link org.apache.tapestry.annotations.Environmental}
+     * service.  Filters often provide {@link Environmental} services needed by components as they render. <dl>
+     * <dt>PageRenderSupport</dt>  <dd>Provides {@link PageRenderSupport}</dd> <dt>ZoneSetup</dt> <dd>Provides {@link
+     * ZoneSetup}</dd> <dt>Heartbeat</dt> <dd>Provides {@link org.apache.tapestry.services.Heartbeat}</dd>
+     * <dt>DefaultValidationDecorator</dt> <dd>Provides {@link org.apache.tapestry.ValidationDecorator} (as an instance
+     * of {@link org.apache.tapestry.internal.DefaultValidationDecorator})</dd> </dl>
      */
     public void contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration,
 
@@ -1450,7 +1369,8 @@
      * @param configuration filters for the service
      * @param renderQueue   does most of the work
      * @return the service
-     * @see #contributePartialMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset, ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource, AssetSource)
+     * @see #contributePartialMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset,
+     *      ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource, AssetSource)
      */
     public PartialMarkupRenderer buildPartialMarkupRenderer(Logger logger,
                                                             List<PartialMarkupRendererFilter> configuration,
@@ -1470,17 +1390,14 @@
     }
 
     /**
-     * Contributes {@link PartialMarkupRendererFilter}s used when rendering a partial Ajax response.  This
-     * is an analog to {@link #contributeMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset, org.apache.tapestry.Asset, ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource, AssetSource)} }
-     * and overlaps it to some degree.
-     * <dl>
-     * <dt>   PageRenderSupport     </dt>
-     * <dd>Provides {@link org.apache.tapestry.PageRenderSupport}</dd>
-     * <dt>ZoneSetup</dt> <dd>Provides {@link ZoneSetup}</dd>
+     * Contributes {@link PartialMarkupRendererFilter}s used when rendering a partial Ajax response.  This is an analog
+     * to {@link #contributeMarkupRenderer(org.apache.tapestry.ioc.OrderedConfiguration, org.apache.tapestry.Asset,
+     * org.apache.tapestry.Asset, ValidationMessagesSource, org.apache.tapestry.ioc.services.SymbolSource, AssetSource)}
+     * } and overlaps it to some degree. <dl> <dt>   PageRenderSupport     </dt> <dd>Provides {@link
+     * org.apache.tapestry.PageRenderSupport}</dd> <dt>ZoneSetup</dt> <dd>Provides {@link ZoneSetup}</dd>
      * <dt>Heartbeat</dt> <dd>Provides {@link org.apache.tapestry.services.Heartbeat}</dd>
-     * <dt>DefaultValidationDecorator</dt>
-     * <dd>Provides {@link org.apache.tapestry.ValidationDecorator} (as an instance of {@link org.apache.tapestry.internal.DefaultValidationDecorator})</dd>
-     * </dl>
+     * <dt>DefaultValidationDecorator</dt> <dd>Provides {@link org.apache.tapestry.ValidationDecorator} (as an instance
+     * of {@link org.apache.tapestry.internal.DefaultValidationDecorator})</dd> </dl>
      */
     public void contributePartialMarkupRenderer(OrderedConfiguration<PartialMarkupRendererFilter> configuration,
 
@@ -1570,15 +1487,9 @@
     }
 
     /**
-     * Contributes several strategies:
-     * <dl>
-     * <dt>session
-     * <dd>Values are stored in the {@link Session}
-     * <dt>flash
-     * <dd>Values are stored in the {@link Session}, until the next request (for the page)
-     * <dt>client
-     * <dd>Values are encoded into URLs (or hidden form fields)
-     * </dl>
+     * Contributes several strategies: <dl> <dt>session <dd>Values are stored in the {@link Session} <dt>flash
+     * <dd>Values are stored in the {@link Session}, until the next request (for the page) <dt>client <dd>Values are
+     * encoded into URLs (or hidden form fields) </dl>
      */
     public void contributePersistentFieldManager(MappedConfiguration<String, PersistentFieldStrategy> configuration,
 
@@ -1607,11 +1518,7 @@
     }
 
     /**
-     * Contributes {@link ValueEncoderFactory}s for types:
-     * <ul>
-     * <li>String
-     * <li>Enum
-     * </ul>
+     * Contributes {@link ValueEncoderFactory}s for types: <ul> <li>String <li>Enum </ul>
      *
      * @param configuration
      */
@@ -1630,8 +1537,8 @@
     }
 
     /**
-     * Builds the component action request handler for traditional (non-Ajax) requests. These typically
-     * result in a redirect to a Tapestry render URL.
+     * Builds the component action request handler for traditional (non-Ajax) requests. These typically result in a
+     * redirect to a Tapestry render URL.
      *
      * @see org.apache.tapestry.internal.services.ComponentActionRequestHandlerImpl
      */
@@ -1644,9 +1551,8 @@
     }
 
     /**
-     * Builds the action request handler for Ajax requests, based on
-     * {@link org.apache.tapestry.internal.services.AjaxComponentActionRequestHandler}.  Filters on
-     * the request handler are supported here as well.
+     * Builds the action request handler for Ajax requests, based on {@link org.apache.tapestry.internal.services.AjaxComponentActionRequestHandler}.
+     * Filters on the request handler are supported here as well.
      */
     @Marker(Ajax.class)
     public ComponentActionRequestHandler buildAjaxComponentActionRequestHandler(
@@ -1657,9 +1563,9 @@
     }
 
     /**
-     * Configures the extensions that will require a digest to be downloaded via the asset
-     * dispatcher. Most resources are "safe", they don't require a digest. For unsafe resources, the
-     * digest is incorporated into the URL to ensure that the client side isn't just "fishing".
+     * Configures the extensions that will require a digest to be downloaded via the asset dispatcher. Most resources
+     * are "safe", they don't require a digest. For unsafe resources, the digest is incorporated into the URL to ensure
+     * that the client side isn't just "fishing".
      * <p/>
      * The extensions must be all lower case.
      * <p/>
@@ -1717,6 +1623,11 @@
 
         configuration.add("tapestry.default-stylesheet", "org/apache/tapestry/default.css");
 
+        configuration.add("tapestry.page-pool.soft-limit", "5");
+        configuration.add("tapestry.page-pool.soft-wait", "10 ms");
+        configuration.add("tapestry.page-pool.hard-limit", "20");
+        configuration.add("tapestry.page-pool.active-window", "10 m");
+
         configuration.add(TapestryConstants.FORCE_FULL_URIS_SYMBOL, "false");
 
 // This is designed to make it easy to keep synchronized with script.aculo.ous. As we
@@ -1766,23 +1677,27 @@
         return transformer;
     }
 
-    public PagePool build(Logger logger, PageLoader pageLoader, ComponentMessagesSource componentMessagesSource,
-                          ComponentClassResolver resolver)
+    public PagePool build(PageLoader pageLoader, ComponentMessagesSource componentMessagesSource,
+                          ServiceResources resources)
     {
-        PagePoolImpl service = new PagePoolImpl(logger, pageLoader, _threadLocale, resolver);
+        PagePoolImpl service = resources.autobuild(PagePoolImpl.class);
 
-// This covers invalidations due to changes to classes
+        // This covers invalidations due to changes to classes
 
         pageLoader.addInvalidationListener(service);
 
-// This covers invalidation due to changes to message catalogs (properties files)
+        // This covers invalidation due to changes to message catalogs (properties files)
 
         componentMessagesSource.addInvalidationListener(service);
 
-// ... and this covers invalidations due to changes to templates
+        // ... and this covers invalidations due to changes to templates
 
         _componentTemplateSource.addInvalidationListener(service);
 
+        // Give the service a chance to clean up its own cache periodically as well
+
+        _updateListenerHub.addUpdateListener(service);
+
         return service;
     }
 
@@ -1869,12 +1784,12 @@
     }
 
     /**
-     * Builds the PropBindingFactory as a chain of command. The terminator of the chain is
-     * responsible for ordinary property names (and property paths). Contributions to the service
-     * cover additional special cases, such as simple literal values.
+     * Builds the PropBindingFactory as a chain of command. The terminator of the chain is responsible for ordinary
+     * property names (and property paths). Contributions to the service cover additional special cases, such as simple
+     * literal values.
      *
-     * @param configuration contributions of special factories for some constants, each contributed factory
-     *                      may return a binding if applicable, or null otherwise
+     * @param configuration contributions of special factories for some constants, each contributed factory may return a
+     *                      binding if applicable, or null otherwise
      */
     public BindingFactory buildPropBindingFactory(List<BindingFactory> configuration,
                                                   PropertyConduitSource propertyConduitSource)
@@ -1887,11 +1802,8 @@
     }
 
     /**
-     * Adds content types for "css" and "js" file extensions.
-     * <dl>
-     * <dt>css</dt> <dd>test/css</dd>
-     * <dt>js</dt> <dd>text/javascript</dd>
-     * </dl>
+     * Adds content types for "css" and "js" file extensions. <dl> <dt>css</dt> <dd>test/css</dd> <dt>js</dt>
+     * <dd>text/javascript</dd> </dl>
      */
     @SuppressWarnings({"JavaDoc"})
     public void contributeResourceStreamer(MappedConfiguration<String, String> configuration)
@@ -1901,9 +1813,9 @@
     }
 
     /**
-     * Adds a listener to the {@link org.apache.tapestry.internal.services.ComponentInstantiatorSource} that clears
-     * the {@link PropertyAccess} and {@link TypeCoercer} caches on a class loader invalidation.  In addition, forces
-     * the realization of {@link ComponentClassResolver} at startup.
+     * Adds a listener to the {@link org.apache.tapestry.internal.services.ComponentInstantiatorSource} that clears the
+     * {@link PropertyAccess} and {@link TypeCoercer} caches on a class loader invalidation.  In addition, forces the
+     * realization of {@link ComponentClassResolver} at startup.
      */
     public void contributeApplicationInitializer(OrderedConfiguration<ApplicationInitializerFilter> configuration,
                                                  final PropertyAccess propertyAccess, final TypeCoercer typeCoercer,

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Sat Jan 12 17:48:32 2008
@@ -1,4 +1,4 @@
-# Copyright 2006, 2007 The Apache Software Foundation
+# Copyright 2006, 2007, 2008 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.
@@ -88,3 +88,6 @@
 base-class-in-wrong-package=Base class %s (super class of %s) is not in a controlled package and is therefore not valid. You should try moving the class to package %s.
 invalid-component-id=Component id '%s' is not valid; component ids must be valid Java identifiers: start with a letter, and consist of letters, numbers and underscores.
 invalid-block-id=Block id '%s' is not valid; block ids must be valid Java identifiers: start with a letter, and consist of letters, numbers and underscores. 
+page-pool-exausted=The page pool for page '%s' (in locale %s) has been exausted: there are %d instances currently being used and no more can be created. \
+  Try increasing the hard limit (symbol tapestry.page-pool.hard-limit) to allow additional instances to be created, \
+  or increasing the soft wait (symbol tapestry.page-pool.soft-wait) to trade away some throughput for more efficient use of page instances.
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/conf.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/conf.apt?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/conf.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/conf.apt Sat Jan 12 17:48:32 2008
@@ -125,16 +125,48 @@
     The path to the embedded copy of {{{http://script.aculo.us/}script.aculo.us}} packaged with Tapestry. This value may be overridden
     to use a different version of the script.aculo.us library. Tapestry's default version is 1.7.0 (including Prototype 1.5.0).
 
+  [tapestry.page-pool.active-window]
+    The time interval that an instantiated page instance may be cached before being removed. As pages are
+    returned to the pool, they are time stamped. Periodically (as per the file check interval),
+    the pool is scanned for page instances that have not been used recently; those that are outside
+    the active window are discarded.  This is used to help free up unnecessary page instances after
+    a request surge.
+
+    The default is "10 m" (10 minutes).
+    
+  [tapestry.page-pool.hard-limit]
+    The absolute maximum number of page instances (for a particular page name / locale combination) that Tapestry
+    will create at any time.  If this number is reached, then requests will fail because a page instance is not
+    available ... this can happen as part of a denial of service attack.  For this value to have any meaning, it
+    should be lower than the number of threads that the servlet container is configured to use when processing
+    requests.
+
+    The default is 20 page instances.
+
+  [tapestry.page-pool.soft-limit]
+    The number of pages in the page pool (for a given page name / locale combination) before which Tapestry will
+    start to wait for existing pages to be made available.  Under this limit of pages, Tapestry will simply
+    create a new page instance if no existing instance is readily available.  Once the soft limit is reached,
+    Tapestry will wait a short period of time (the soft wait interval) to see if an existing page instance
+    is made available.  It will then create a new page instance (unless the hard limit has been reached).
+
+    The default is 5 page instances.
+
+  [tapestry.page-pool.soft-wait]
+    The time interval that Tapestry will wait for a page instance to come available before deciding whether to create
+    a new instance.  The default is "10 ms".
+
+
+
+
+  [tapestry.start-page-name]
+    The logical name of the start page, the page that is rendered for the <root URL>.  This is normally "start".
+    
+
   [tapestry.supported-locales]
     A comma-separated list of supported locales.  Incoming requests as "narrowed" to one of these locales, based on closest match.
     If no match can be found, the first locale in the list is treated as the default.
-    
+
     The default is (currently) "en,it,zn_CH".  As the community contributes new localizations of the necessary messages files,
     this list will expand.  Note that the Tapestry quickstart archetype overrides the factory default, forcing the
     application to be localized only for "en".
-    
-  [tapestry.start-page-name]
-    The logical name of the start page, the page that is rendered for the <root URL>.  This is normally "start".
-    
-
-  
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/lifecycle.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/lifecycle.apt?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/lifecycle.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/lifecycle.apt Sat Jan 12 17:48:32 2008
@@ -1,7 +1,107 @@
  ----
- Page Lifecycle Methods
+ Page Lifecycle
  ----
- 
+
+Page Lifecycle
+
+   In Tapestry, you are free to develop your presentation objects, page and components classes,
+   as ordinary objects, complete with instance variables and so forth.
+
+   This is somewhat revolutionary in terms of web development in Java.  Using servlets, or Struts,
+   your presentation objects (Servlets, or Struts Actions, or the equivalent in other frameworks)
+   are <stateless singletons>.  That is, a <single> instance is created, and all incoming requests
+   are threaded through that single instance.
+
+   Because multiple requests are handled by many different threads, this means that the single instance's
+   variable are useless ... any value written into an instance variable would immediately be
+   overwritten by a different thread.  Thus, it is necessary to use the
+   Servlet API's HttpServletRequest object to store per-request data, and the HttpSession object
+   to store data between requests.
+
+   Tapestry takes a very different approach.
+
+   In Tapestry, you will have many different instances of any particular page, each either in use
+   for a single request (on a single thread), or waiting in a <page pool> to be used.
+
+   By reserving page instances to particular threads, all the difficult, ugly issues related to
+   multi-threading go by the wayside.  Instead, familiar, simple coding practices (using ordinary methods and fields)
+   can be used.
+
+   However, there's a risk: it would be a disaster if data could "bleed" from one request to another. Imagine
+   the outcome in a banking application if the first user's account number and password became the default
+   for the second user to reach the application!
+
+   Tapestry takes special care to purge all instance variables back to their default value at the end of each request.
+
+   The end result is that all pages in the pool are entirely equivalent to each other; it doesn't matter which
+   instance is used for processing any particular request.
+
+Comparison to JavaServer Pages
+
+   JSPs also use a caching mechanism; the JSP itself is compiled into a Java servlet class, and acts as a singleton.
+
+   However, the individual JSP tags are pooled.
+
+   This is one of the areas where Tapestry can significantly outperform JSPs.  Much of the code inside a compiled
+   JSP class concerns
+   getting tags from a tag pool, configuring the properties of the tag instance, using the tag instance, then
+   cleaning up the tag instance and putting it back in the pool.
+
+   The operations Tapestry does once per request are instead executed dozens or potentially hundreds of times (dependending
+   the complexity of the page, and if any nested loops occur).
+
+   Pooling JSP tags is simply the wrong granularity.
+
+   Tapestry can also take advantage of its more coarse grained caching to optimize how data moves, via parameters,
+   between components. This means that Tapestry pages will actually speed up after they render the first time.
+
+Page Pool Configuration
+
+   Tapestry's page pool is used to store page instances.  The pool is "keyed" on the name of the page (such as "start")
+   and the <locale> for the page (such as "en" or "fr").
+
+   Within each key, Tapestry tracks the number of page instances that have been created, as well as the number
+   that are in use (currently attached to a request).
+
+   When a page is first accessed in a request, it is taken from the pool.  Tapestry has some
+   {{{conf.html}configuration values}} that control the details of how and when page instances are created.
+
+   * If a free page instance is available, the page is marked in use and attached to the request.
+
+   * If there are fewer page instances than the <soft limit>, then a new page instance is simply created and
+     attached to the request.
+
+   * If the soft limit has been reached, Tapestry will wait for a short period of time for
+     a page instance to become available.
+
+   * If the hard limit has been reached, Tapestry will throw an exception rather than create a new page instance.
+
+   * Otherwise, Tapestry will create a new page instance.
+
+   []
+
+   Thus a busy application will initially create pages up-to the soft limit (which defaults to five page
+   instances).  If the application continues to be pounded with requests, it will slow its request
+   processing, using the soft wait time in an attempt to reuse an existing page instance.
+
+   A truly busy application will continue to create new page instances as needed until the
+   hard limit is reached.
+
+   Remember that all these configuration values are  per key: the combination of page name and locale.
+   Thus even with a hard limit of 20, you may have 20 start page instances for locale en and 20 start page
+   instances for locale fr (if you application is configured to support English and French).  Likewise,
+   you may have 20 instances for the start page, and 20 instances for the newaccount page.
+
+   Pooled pages are stored using <soft references>; this means that the garbage collector is free to reclaim
+   a page instance (that isn't actively being used) at any time. Further, Tapestry will release
+   page instances that haven't been used recently.
+
+   The end result is that you have quite a degree of tuning control over the process.  If memory is a limitation
+   and throughput can be sacrificed, try lowering the soft and hard limit and increasing the soft wait.
+
+   If performance is absolute and you have lots of memory, then increase the soft and hard limit and reduce 
+   the soft wait.
+
 Page Lifecycle Methods
 
   There are a few situations where it is useful for a component to perform some operations, usually some kind of initialization or

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolCacheTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolCacheTest.java?rev=611522&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolCacheTest.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolCacheTest.java Sat Jan 12 17:48:32 2008
@@ -0,0 +1,276 @@
+// Copyright 2008 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.tapestry.internal.services;
+
+import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.testng.annotations.Test;
+
+import java.util.Locale;
+
+public class PagePoolCacheTest extends InternalBaseTestCase
+{
+    private static final String PAGE_NAME = "mypage";
+    private static final Locale LOCALE = Locale.ENGLISH;
+
+    @Test
+    public void inside_of_soft_limit()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        Page page2 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 5, 100, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        verify();
+    }
+
+    @Test
+    public void reuse_existing_page()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        Page page2 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 5, 100, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        cache.release(page1);
+
+        assertSame(cache.checkout(), page1);
+
+        verify();
+    }
+
+    @Test
+    public void remove_does_not_reuse_page()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        Page page2 = mockPage();
+        Page page3 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page3);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 5, 100, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        cache.remove(page1);
+
+        assertSame(cache.checkout(), page3);
+
+        verify();
+    }
+
+    @Test
+    public void new_instance_after_soft_wait()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        Page page2 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 1, 500, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+
+        long start = System.currentTimeMillis();
+
+        assertSame(cache.checkout(), page2);
+
+        long elapsed = System.currentTimeMillis() - start;
+
+        // Fudging a bit because Java clocks are notoriously innaccurate
+
+        assertTrue(elapsed > 490, "A delay should have occured.");
+
+        verify();
+    }
+
+    @Test
+    public void hard_limit_failure()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 1, 10, 1, 1000);
+
+        assertSame(cache.checkout(), page1);
+
+        try
+        {
+            cache.checkout();
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertMessageContains(ex,
+                                  "The page pool for page 'mypage' (in locale en) has been exausted: there are 1 instances currently being used and no more can be created");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void page_released_by_other_thread() throws Exception
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        final Page page2 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+
+        replay();
+
+        final PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 1, 1000, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        Runnable r = new Runnable()
+        {
+            public void run()
+            {
+                sleep(20);
+
+                cache.release(page2);
+            }
+        };
+
+        new Thread(r).start();
+
+        assertSame(cache.checkout(), page2);
+
+        verify();
+    }
+
+    @Test
+    public void page_removed_by_other_thread_is_not_used()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        final Page page2 = mockPage();
+        Page page3 = mockPage();
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page3);
+
+        replay();
+
+        final PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 1, 100, 20, 1000);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        Runnable r = new Runnable()
+        {
+            public void run()
+            {
+                sleep(20);
+
+                cache.remove(page2);
+            }
+        };
+
+        new Thread(r).start();
+
+        assertSame(cache.checkout(), page3);
+
+        verify();
+    }
+
+    @Test
+    public void cleanup()
+    {
+        PageLoader loader = mockPageLoader();
+        Page page1 = mockPage();
+        Page page2 = mockPage();
+        Page page3 = mockPage();
+
+
+        train_loadPage(loader, PAGE_NAME, LOCALE, page1);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page2);
+        train_loadPage(loader, PAGE_NAME, LOCALE, page3);
+
+        replay();
+
+        PagePoolCache cache = new PagePoolCache(PAGE_NAME, LOCALE, loader, 5, 100, 20, 50);
+
+        assertSame(cache.checkout(), page1);
+        assertSame(cache.checkout(), page2);
+
+        cache.release(page1);
+
+        // Sleep longer than the active window (10)
+
+        sleep(75);
+
+        cache.release(page2);
+
+        cache.cleanup();
+
+        assertSame(cache.checkout(), page2);
+
+        // Page3 is created because page1 was culled as too old.
+
+        assertSame(cache.checkout(), page3);
+
+        verify();
+    }
+
+    private static void sleep(long time)
+    {
+        try
+        {
+            Thread.sleep(time);
+        }
+        catch (Exception ex)
+        {
+            // Ignore.
+        }
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolImplTest.java?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PagePoolImplTest.java Sat Jan 12 17:48:32 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 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.
@@ -51,7 +51,7 @@
 
         replay();
 
-        PagePool pool = new PagePoolImpl(logger, loader, tl, resolver);
+        PagePool pool = new PagePoolImpl(logger, loader, tl, resolver, 5, 0, 20, 600000);
 
         assertSame(page, pool.checkout(INPUT_PAGE_NAME));
 
@@ -76,7 +76,7 @@
 
         replay();
 
-        PagePool pool = new PagePoolImpl(logger, loader, tl, resolver);
+        PagePool pool = new PagePoolImpl(logger, loader, tl, resolver, 5, 0, 20, 600000);
 
         assertSame(pool.checkout(INPUT_PAGE_NAME), page1);
 
@@ -117,6 +117,8 @@
         Logger logger = mockLogger();
 
         train_detached(page, true);
+        train_getLogicalName(page, "dirty");
+        train_getLocale(page, Locale.ENGLISH);
 
         logger.error(contains("is dirty, and will be discarded"));
 
@@ -125,7 +127,7 @@
 
         replay();
 
-        PagePool pool = new PagePoolImpl(logger, loader, null, null);
+        PagePool pool = new PagePoolImpl(logger, loader, null, null, 5, 0, 20, 600000);
 
         pool.release(page);
 

Modified: tapestry/tapestry5/trunk/tapestry5.ipr
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry5.ipr?rev=611522&r1=611521&r2=611522&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry5.ipr (original)
+++ tapestry/tapestry5/trunk/tapestry5.ipr Sat Jan 12 17:48:32 2008
@@ -33,6 +33,7 @@
         <option name="THROWS_LIST_WRAP" value="1" />
         <option name="THROWS_KEYWORD_WRAP" value="1" />
         <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+        <option name="WRAP_COMMENTS" value="true" />
         <option name="PARAMETER_ANNOTATION_WRAP" value="5" />
         <ADDITIONAL_INDENT_OPTIONS fileType="js">
           <option name="INDENT_SIZE" value="4" />