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 throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex); 182 } 183 184 185 if (result.getEngine() == null) 186 { 187 // This call will also fire events to any PageAttachListeners 188 189 result.attach(engine, cycle); 190 } 191 192 return result; 193 } 194 195 /** 196 * Returns the page to the appropriate pool. Invokes {@link IPage#detach()}. 197 */ 198 199 public void releasePage(IPage page) 200 { 201 Tapestry.clearMethodInvocations(); 202 203 page.detach(); 204 205 Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page); 206 207 PageKey key = buildKey(page); 208 209 try 210 { 211 _pool.returnObject(key, page); 212 213 } catch (Exception ex) 214 { 215 throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex); 216 } 217 } 218 219 public void resetEventDidOccur() 220 { 221 _pool.clear(); 222 } 223 224 public void reportStatus(ReportStatusEvent event) 225 { 226 event.title(_serviceId); 227 228 event.section("Page Pool"); 229 230 event.property("active", _pool.getNumActive()); 231 event.property("idle", _pool.getNumIdle()); 232 } 233 234 public void setServiceId(String serviceId) 235 { 236 _serviceId = serviceId; 237 } 238 239 public void setRequestCycle(IRequestCycle cycle) 240 { 241 _cycle = cycle; 242 } 243 244 /** @since 4.0 */ 245 246 public void setClassResolver(ClassResolver resolver) 247 { 248 _classResolver = resolver; 249 } 250 251 /** @since 4.0 */ 252 253 public void setPageSpecificationResolver(PageSpecificationResolver resolver) 254 { 255 _pageSpecificationResolver = resolver; 256 } 257 258 /** @since 4.0 */ 259 260 public void setLoader(IPageLoader loader) 261 { 262 _loader = loader; 263 } 264 265 public void setPropertySource(IPropertySource propertySource) 266 { 267 _propertySource = propertySource; 268 } 269 }