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 }