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