| // Copyright 2004 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.pageload; |
| |
| import org.apache.commons.lang.builder.ToStringBuilder; |
| import org.apache.tapestry.IEngine; |
| import org.apache.tapestry.IPage; |
| import org.apache.tapestry.IRequestCycle; |
| import org.apache.tapestry.IResourceResolver; |
| import org.apache.tapestry.Tapestry; |
| import org.apache.tapestry.engine.IMonitor; |
| import org.apache.tapestry.engine.IPageSource; |
| import org.apache.tapestry.resolver.PageSpecificationResolver; |
| import org.apache.tapestry.util.MultiKey; |
| import org.apache.tapestry.util.pool.Pool; |
| |
| /** |
| * A source for pages for a particular application. Each application |
| * should have its own <code>PageSource</code>, storing it into the |
| * {@link javax.servlet.ServletContext} using a unique key (usually built from |
| * the application name). |
| * |
| * <p>The <code>PageSource</code> acts as a pool for {@link IPage} instances. |
| * Pages are retrieved from the pool using {@link #getPage(IRequestCycle, String, IMonitor)} |
| * and are later returned to the pool using {@link #releasePage(IPage)}. |
| * |
| * |
| * <p>TBD: Pooled pages stay forever. Need a strategy for cleaning up the pool, |
| * tracking which pages have been in the pool the longest, etc. A mechanism |
| * for reporting pool statistics would be useful. |
| * |
| * @author Howard Lewis Ship |
| * |
| **/ |
| |
| public class PageSource implements IPageSource |
| { |
| /** |
| * Key used to find PageLoader instances in the Pool. |
| * |
| **/ |
| |
| private static final String PAGE_LOADER_POOL_KEY = "org.apache.tapestry.PageLoader"; |
| |
| /** |
| * Key used to find {@link PageSpecificationResolver} instance |
| * in the pool. |
| */ |
| |
| private static final String PAGE_SPECIFICATION_RESOLVER_KEY = |
| "org.apache.tapestry.PageSpecificationResolver"; |
| |
| private IResourceResolver _resolver; |
| |
| /** |
| * The pool of {@link IPage}s. The key is a {@link MultiKey}, |
| * built from the page name and the page locale. This is a reference |
| * to a shared pool. |
| * |
| * @see IEngine#getPool() |
| * |
| **/ |
| |
| private Pool _pool; |
| |
| public PageSource(IEngine engine) |
| { |
| _resolver = engine.getResourceResolver(); |
| _pool = engine.getPool(); |
| } |
| |
| public IResourceResolver getResourceResolver() |
| { |
| return _resolver; |
| } |
| |
| /** |
| * Builds a key for a named page in the application's current locale. |
| * |
| **/ |
| |
| protected MultiKey buildKey(IEngine engine, String pageName) |
| { |
| Object[] keys; |
| |
| keys = new Object[] { pageName, engine.getLocale()}; |
| |
| // Don't make a copy, this array is just for the MultiKey. |
| |
| return new MultiKey(keys, false); |
| } |
| |
| /** |
| * Builds a key from an existing page, using the page's name and locale. This is |
| * used when storing a page into the pool. |
| * |
| **/ |
| |
| protected MultiKey buildKey(IPage page) |
| { |
| Object[] keys; |
| |
| keys = new Object[] { page.getPageName(), page.getLocale()}; |
| |
| // Don't make a copy, this array is just for the MultiKey. |
| |
| return new MultiKey(keys, false); |
| } |
| |
| /** |
| * Gets the page from a pool, or otherwise loads the page. This operation |
| * is threadsafe. |
| * |
| **/ |
| |
| public IPage getPage(IRequestCycle cycle, String pageName, IMonitor monitor) |
| { |
| IEngine engine = cycle.getEngine(); |
| Object key = buildKey(engine, pageName); |
| IPage result = (IPage) _pool.retrieve(key); |
| |
| if (result == null) |
| { |
| monitor.pageCreateBegin(pageName); |
| |
| // Resolvers are not threadsafe, so we get one from |
| // the pool or create as needed. |
| |
| PageSpecificationResolver pageSpecificationResolver = |
| getPageSpecificationResolver(cycle); |
| |
| pageSpecificationResolver.resolve(cycle, pageName); |
| |
| // Likewise PageLoader |
| |
| PageLoader loader = getPageLoader(cycle); |
| |
| try |
| { |
| result = |
| loader.loadPage( |
| pageSpecificationResolver.getSimplePageName(), |
| pageSpecificationResolver.getNamespace(), |
| cycle, |
| pageSpecificationResolver.getSpecification()); |
| } |
| finally |
| { |
| discardPageLoader(loader); |
| discardPageSpecificationResolver(pageSpecificationResolver); |
| } |
| |
| monitor.pageCreateEnd(pageName); |
| } |
| else |
| { |
| // The page loader attaches the engine, but a page from |
| // the pool needs to be explicitly attached. |
| |
| result.attach(engine); |
| result.setRequestCycle(cycle); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Invoked to obtain an instance of |
| * {@link PageLoader}. An instance if aquired from the pool or, |
| * if none are available, created fresh. |
| * |
| * @since 3.0 |
| * |
| **/ |
| |
| protected PageLoader getPageLoader(IRequestCycle cycle) |
| { |
| PageLoader result = (PageLoader) _pool.retrieve(PAGE_LOADER_POOL_KEY); |
| |
| if (result == null) |
| result = new PageLoader(cycle); |
| |
| return result; |
| } |
| |
| /** |
| * Invoked once the {@link PageLoader} is not |
| * longer needed; it is then returned to the pool. |
| * |
| * @since 3.0 |
| * |
| **/ |
| |
| protected void discardPageLoader(PageLoader loader) |
| { |
| _pool.store(PAGE_LOADER_POOL_KEY, loader); |
| } |
| |
| /** |
| * Invoked to obtain an instance of {@link PageSpecificationResolver}. |
| * An instance is acquired form the pool or, if none are available, |
| * a new one is instantiated. |
| * |
| * @since 3.0 |
| */ |
| |
| protected PageSpecificationResolver getPageSpecificationResolver(IRequestCycle cycle) |
| { |
| PageSpecificationResolver result = |
| (PageSpecificationResolver) _pool.retrieve(PAGE_SPECIFICATION_RESOLVER_KEY); |
| |
| if (result == null) |
| result = new PageSpecificationResolver(cycle); |
| |
| return result; |
| } |
| |
| /** |
| * Invoked once the {@link PageSpecificationResolver} is no longer |
| * needed, it is returned to the pool. |
| * |
| * @since 3.0 |
| */ |
| |
| protected void discardPageSpecificationResolver(PageSpecificationResolver resolver) |
| { |
| _pool.store(PAGE_SPECIFICATION_RESOLVER_KEY, resolver); |
| } |
| |
| /** |
| * Returns the page to the appropriate pool. Invokes |
| * {@link IPage#detach()}. |
| * |
| **/ |
| |
| public void releasePage(IPage page) |
| { |
| Tapestry.clearMethodInvocations(); |
| |
| page.detach(); |
| |
| Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page); |
| |
| _pool.store(buildKey(page), page); |
| } |
| |
| /** |
| * Invoked (during testing primarily) to release the entire pool |
| * of pages, and the caches of bindings and assets. |
| * |
| **/ |
| |
| public synchronized void reset() |
| { |
| _pool.clear(); |
| } |
| |
| public String toString() |
| { |
| ToStringBuilder builder = new ToStringBuilder(this); |
| |
| builder.append("pool", _pool); |
| builder.append("resolver", _resolver); |
| |
| return builder.toString(); |
| } |
| |
| } |