001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry.pageload;
016
017import org.apache.hivemind.ClassResolver;
018import org.apache.tapestry.IEngine;
019import org.apache.tapestry.IPage;
020import org.apache.tapestry.IRequestCycle;
021import org.apache.tapestry.Tapestry;
022import org.apache.tapestry.engine.IMonitor;
023import org.apache.tapestry.engine.IPageLoader;
024import org.apache.tapestry.engine.IPageSource;
025import org.apache.tapestry.resolver.PageSpecificationResolver;
026import org.apache.tapestry.services.ObjectPool;
027import org.apache.tapestry.util.MultiKey;
028
029/**
030 * A source for pages for a particular application. Each application should have its own
031 * <code>PageSource</code>, storing it into the {@link javax.servlet.ServletContext}using a
032 * unique key (usually built from the application name).
033 * <p>
034 * The <code>PageSource</code> acts as a pool for {@link IPage}instances. Pages are retrieved
035 * from the pool using {@link #getPage(IRequestCycle, String, IMonitor)}and are later returned to
036 * the pool using {@link #releasePage(IPage)}.
037 * <p>
038 * TBD: Pooled pages stay forever. Need a strategy for cleaning up the pool, tracking which pages
039 * have been in the pool the longest, etc.
040 * 
041 * @author Howard Lewis Ship
042 */
043
044public class PageSource implements IPageSource
045{
046    /** set by container */
047    private ClassResolver _classResolver;
048
049    /** @since 4.0 */
050    private PageSpecificationResolver _pageSpecificationResolver;
051
052    /** @since 4.0 */
053
054    private IPageLoader _loader;
055
056    /**
057     * The pool of {@link IPage}s. The key is a {@link MultiKey}, built from the page name and the
058     * page locale. This is a reference to a shared pool.
059     */
060
061    private ObjectPool _pool;
062
063    public ClassResolver getClassResolver()
064    {
065        return _classResolver;
066    }
067
068    /**
069     * Builds a key for a named page in the application's current locale.
070     */
071
072    protected MultiKey buildKey(IEngine engine, String pageName)
073    {
074        Object[] keys;
075
076        keys = new Object[]
077        { pageName, engine.getLocale() };
078
079        // Don't make a copy, this array is just for the MultiKey.
080
081        return new MultiKey(keys, false);
082    }
083
084    /**
085     * Builds a key from an existing page, using the page's name and locale. This is used when
086     * storing a page into the pool.
087     */
088
089    protected MultiKey buildKey(IPage page)
090    {
091        Object[] keys;
092
093        keys = new Object[]
094        { page.getPageName(), page.getLocale() };
095
096        // Don't make a copy, this array is just for the MultiKey.
097
098        return new MultiKey(keys, false);
099    }
100
101    /**
102     * Gets the page from a pool, or otherwise loads the page. This operation is threadsafe.
103     */
104
105    public IPage getPage(IRequestCycle cycle, String pageName, IMonitor monitor)
106    {
107        IEngine engine = cycle.getEngine();
108        Object key = buildKey(engine, pageName);
109        IPage result = (IPage) _pool.get(key);
110
111        if (result == null)
112        {
113            monitor.pageCreateBegin(pageName);
114
115            _pageSpecificationResolver.resolve(cycle, pageName);
116
117            // The loader is responsible for invoking attach(),
118            // and for firing events to PageAttachListeners
119
120            result = _loader.loadPage(
121                    _pageSpecificationResolver.getSimplePageName(),
122                    _pageSpecificationResolver.getNamespace(),
123                    cycle,
124                    _pageSpecificationResolver.getSpecification());
125
126            monitor.pageCreateEnd(pageName);
127        }
128        else
129        {
130            // But for pooled pages, we are responsible.
131            // This call will also fire events to any PageAttachListeners
132
133            result.attach(engine, cycle);
134        }
135
136        return result;
137    }
138
139    /**
140     * Returns the page to the appropriate pool. Invokes {@link IPage#detach()}.
141     */
142
143    public void releasePage(IPage page)
144    {
145        Tapestry.clearMethodInvocations();
146
147        page.detach();
148
149        Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page);
150
151        _pool.store(buildKey(page), page);
152    }
153
154    /** @since 4.0 */
155
156    public void setPool(ObjectPool pool)
157    {
158        _pool = pool;
159    }
160
161    /** @since 4.0 */
162
163    public void setClassResolver(ClassResolver resolver)
164    {
165        _classResolver = resolver;
166    }
167
168    /** @since 4.0 */
169
170    public void setPageSpecificationResolver(PageSpecificationResolver resolver)
171    {
172        _pageSpecificationResolver = resolver;
173    }
174
175    /** @since 4.0 */
176
177    public void setLoader(IPageLoader loader)
178    {
179        _loader = loader;
180    }
181
182}