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    
015    package org.apache.tapestry.pageload;
016    
017    import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;
018    import org.apache.commons.pool.impl.GenericKeyedObjectPool;
019    import org.apache.hivemind.ApplicationRuntimeException;
020    import org.apache.hivemind.ClassResolver;
021    import org.apache.hivemind.events.RegistryShutdownListener;
022    import org.apache.tapestry.IEngine;
023    import org.apache.tapestry.IPage;
024    import org.apache.tapestry.IRequestCycle;
025    import org.apache.tapestry.Tapestry;
026    import org.apache.tapestry.engine.IPageLoader;
027    import org.apache.tapestry.engine.IPageSource;
028    import org.apache.tapestry.engine.IPropertySource;
029    import org.apache.tapestry.event.ReportStatusEvent;
030    import org.apache.tapestry.event.ReportStatusListener;
031    import org.apache.tapestry.event.ResetEventListener;
032    import org.apache.tapestry.internal.pageload.PageKey;
033    import org.apache.tapestry.resolver.PageSpecificationResolver;
034    import org.apache.tapestry.util.MultiKey;
035    
036    /**
037     * A source for pages for a particular application. Each application should have its own
038     * <code>PageSource</code>, storing it into the {@link javax.servlet.ServletContext}using a
039     * unique key (usually built from the application name).
040     * <p>
041     * The <code>PageSource</code> acts as a pool for {@link IPage}instances. Pages are retrieved
042     * from the pool using {@link #getPage(IRequestCycle, String)}and are later returned to
043     * the pool using {@link #releasePage(IPage)}.
044     * <p>
045     * TBD: Pooled pages stay forever. Need a strategy for cleaning up the pool, tracking which pages
046     * have been in the pool the longest, etc.
047     *
048     * @author Howard Lewis Ship
049     */
050    
051    public class PageSource extends BaseKeyedPoolableObjectFactory implements IPageSource, ResetEventListener, ReportStatusListener, RegistryShutdownListener {
052        
053        /** set by container. */
054        private ClassResolver _classResolver;
055    
056        /** @since 4.0 */
057        private PageSpecificationResolver _pageSpecificationResolver;
058    
059        /** @since 4.0 */
060    
061        private IPageLoader _loader;
062    
063        private IPropertySource _propertySource;
064    
065        private String _serviceId;
066    
067        /**
068         * Thread safe reference to current request.
069         */
070        private IRequestCycle _cycle;
071    
072        static final long MINUTE = 1000 * 60;
073        
074        /**
075         * The pool of {@link IPage}s. The key is a {@link MultiKey}, built from the page name and the
076         * page locale.
077         */
078        GenericKeyedObjectPool _pool;
079    
080        public void initializeService()
081        {
082            _pool = new GenericKeyedObjectPool(this);
083    
084            _pool.setMaxActive(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-max-active")));
085            _pool.setMaxIdle(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-max-idle")));
086    
087            _pool.setMinIdle(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-min-idle")));
088            
089            _pool.setMinEvictableIdleTimeMillis(MINUTE * Long.parseLong(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-evict-idle-page-minutes")));
090            _pool.setTimeBetweenEvictionRunsMillis(MINUTE * Long.parseLong(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-evict-thread-sleep-minutes")));
091            
092            _pool.setTestWhileIdle(false);
093            _pool.setTestOnBorrow(false);
094            _pool.setTestOnReturn(false);
095        }
096    
097        public void registryDidShutdown()
098        {
099            try
100            {
101                _pool.close();
102            } catch (Exception e) {
103                // ignore
104            }
105        }
106    
107        public ClassResolver getClassResolver()
108        {
109            return _classResolver;
110        }
111    
112        /**
113         * Builds a key for a named page in the application's current locale.
114         *
115         * @param engine
116         *          The current engine servicing this request.
117         * @param pageName
118         *          The name of the page to build key for.
119         *
120         * @return The unique key for ths specified page and current {@link java.util.Locale}. 
121         */
122    
123        protected PageKey buildKey(IEngine engine, String pageName)
124        {
125            return new PageKey(pageName, engine.getLocale());
126        }
127    
128        /**
129         * Builds a key from an existing page, using the page's name and locale. This is used when
130         * storing a page into the pool.
131         *
132         * @param page
133         *          The page to build the key for.
134         *
135         * @return The unique key for the specified page instance.
136         */
137    
138        protected PageKey buildKey(IPage page)
139        {
140            return new PageKey(page.getPageName(), page.getLocale());
141        }
142    
143        public Object makeObject(Object key)
144          throws Exception
145        {
146            PageKey pageKey = (PageKey) key;
147    
148            _pageSpecificationResolver.resolve(_cycle, pageKey.getPageName());
149    
150            // The loader is responsible for invoking attach(),
151            // and for firing events to PageAttachListeners
152    
153            return _loader.loadPage(_pageSpecificationResolver.getSimplePageName(),
154                                    _pageSpecificationResolver.getNamespace(),
155                                    _cycle,
156                                    _pageSpecificationResolver.getSpecification());
157        }
158    
159        /**
160         * Gets the page from a pool, or otherwise loads the page. This operation is threadsafe.
161         */
162    
163        public IPage getPage(IRequestCycle cycle, String pageName)
164        {
165    
166            IEngine engine = cycle.getEngine();
167            Object key = buildKey(engine, pageName);
168    
169            IPage result;
170    
171            // lock our page specific key lock first
172            // This is only a temporary measure until a more robust
173            // page pool implementation can be created.
174    
175            try
176            {
177                result = (IPage) _pool.borrowObject(key);
178                
179            } catch (Exception ex)
180            {
181                if (RuntimeException.class.isInstance(ex))
182                    throw (RuntimeException)ex;
183                else
184                    throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex);
185            }
186    
187    
188            if (result.getEngine() == null)
189            {
190                // This call will also fire events to any PageAttachListeners
191                
192                result.attach(engine, cycle);
193            }
194    
195            return result;
196        }
197    
198        /**
199         * Returns the page to the appropriate pool. Invokes {@link IPage#detach()}.
200         */
201    
202        public void releasePage(IPage page)
203        {
204            Tapestry.clearMethodInvocations();
205    
206            page.detach();
207    
208            Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page);
209    
210            PageKey key = buildKey(page);
211    
212            try
213            {
214                _pool.returnObject(key, page);
215    
216            } catch (Exception ex)
217            {
218                if (RuntimeException.class.isInstance(ex))
219                    throw (RuntimeException)ex;
220                else
221                    throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex);
222            }        
223        }
224    
225        public void resetEventDidOccur()
226        {
227            _pool.clear();
228        }
229    
230        public void reportStatus(ReportStatusEvent event)
231        {
232            event.title(_serviceId);
233    
234            event.section("Page Pool");
235    
236            event.property("active", _pool.getNumActive());
237            event.property("idle", _pool.getNumIdle());
238        }
239    
240        public void setServiceId(String serviceId)
241        {
242            _serviceId = serviceId;
243        }
244    
245        public void setRequestCycle(IRequestCycle cycle)
246        {
247            _cycle = cycle;
248        }
249    
250        /** @since 4.0 */
251    
252        public void setClassResolver(ClassResolver resolver)
253        {
254            _classResolver = resolver;
255        }
256    
257        /** @since 4.0 */
258    
259        public void setPageSpecificationResolver(PageSpecificationResolver resolver)
260        {
261            _pageSpecificationResolver = resolver;
262        }
263    
264        /** @since 4.0 */
265    
266        public void setLoader(IPageLoader loader)
267        {
268            _loader = loader;
269        }
270    
271        public void setPropertySource(IPropertySource propertySource)
272        {
273            _propertySource = propertySource;
274        }
275    }