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 }