blob: 7bf38a1a2aaa8ce825682195e76283a81a831d8e [file] [log] [blame]
// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.pageload;
import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.events.RegistryShutdownListener;
import org.apache.tapestry.IEngine;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.engine.IPageLoader;
import org.apache.tapestry.engine.IPageSource;
import org.apache.tapestry.engine.IPropertySource;
import org.apache.tapestry.event.ReportStatusEvent;
import org.apache.tapestry.event.ReportStatusListener;
import org.apache.tapestry.event.ResetEventListener;
import org.apache.tapestry.internal.pageload.PageKey;
import org.apache.tapestry.resolver.PageSpecificationResolver;
import org.apache.tapestry.util.MultiKey;
/**
* A source for pages for a particular application. Each application should have its own
* <code>PageSource</code>, storing it into the {@link javax.servlet.ServletContext}using a
* unique key (usually built from the application name).
* <p>
* The <code>PageSource</code> acts as a pool for {@link IPage}instances. Pages are retrieved
* from the pool using {@link #getPage(IRequestCycle, String)}and are later returned to
* the pool using {@link #releasePage(IPage)}.
* <p>
* TBD: Pooled pages stay forever. Need a strategy for cleaning up the pool, tracking which pages
* have been in the pool the longest, etc.
*
* @author Howard Lewis Ship
*/
public class PageSource extends BaseKeyedPoolableObjectFactory implements IPageSource, ResetEventListener, ReportStatusListener, RegistryShutdownListener {
/** set by container. */
private ClassResolver _classResolver;
/** @since 4.0 */
private PageSpecificationResolver _pageSpecificationResolver;
/** @since 4.0 */
private IPageLoader _loader;
private IPropertySource _propertySource;
private String _serviceId;
/**
* Thread safe reference to current request.
*/
private IRequestCycle _cycle;
static final long MINUTE = 1000 * 60;
/**
* The pool of {@link IPage}s. The key is a {@link MultiKey}, built from the page name and the
* page locale.
*/
GenericKeyedObjectPool _pool;
public void initializeService()
{
_pool = new GenericKeyedObjectPool(this);
_pool.setMaxActive(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-max-active")));
_pool.setMaxIdle(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-max-idle")));
_pool.setMinIdle(Integer.parseInt(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-min-idle")));
_pool.setMinEvictableIdleTimeMillis(MINUTE * Long.parseLong(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-evict-idle-page-minutes")));
_pool.setTimeBetweenEvictionRunsMillis(MINUTE * Long.parseLong(_propertySource.getPropertyValue("org.apache.tapestry.page-pool-evict-thread-sleep-minutes")));
_pool.setTestWhileIdle(false);
_pool.setTestOnBorrow(false);
_pool.setTestOnReturn(false);
}
public void registryDidShutdown()
{
try
{
_pool.close();
} catch (Exception e) {
// ignore
}
}
public ClassResolver getClassResolver()
{
return _classResolver;
}
/**
* Builds a key for a named page in the application's current locale.
*
* @param engine
* The current engine servicing this request.
* @param pageName
* The name of the page to build key for.
*
* @return The unique key for ths specified page and current {@link java.util.Locale}.
*/
protected PageKey buildKey(IEngine engine, String pageName)
{
return new PageKey(pageName, engine.getLocale());
}
/**
* Builds a key from an existing page, using the page's name and locale. This is used when
* storing a page into the pool.
*
* @param page
* The page to build the key for.
*
* @return The unique key for the specified page instance.
*/
protected PageKey buildKey(IPage page)
{
return new PageKey(page.getPageName(), page.getLocale());
}
public Object makeObject(Object key)
throws Exception
{
PageKey pageKey = (PageKey) key;
_pageSpecificationResolver.resolve(_cycle, pageKey.getPageName());
// The loader is responsible for invoking attach(),
// and for firing events to PageAttachListeners
return _loader.loadPage(_pageSpecificationResolver.getSimplePageName(),
_pageSpecificationResolver.getNamespace(),
_cycle,
_pageSpecificationResolver.getSpecification());
}
/**
* Gets the page from a pool, or otherwise loads the page. This operation is threadsafe.
*/
public IPage getPage(IRequestCycle cycle, String pageName)
{
IEngine engine = cycle.getEngine();
Object key = buildKey(engine, pageName);
IPage result;
// lock our page specific key lock first
// This is only a temporary measure until a more robust
// page pool implementation can be created.
try
{
result = (IPage) _pool.borrowObject(key);
} catch (Exception ex)
{
if (RuntimeException.class.isInstance(ex))
throw (RuntimeException)ex;
else
throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex);
}
if (result.getEngine() == null)
{
// This call will also fire events to any PageAttachListeners
result.attach(engine, cycle);
}
return result;
}
/**
* Returns the page to the appropriate pool. Invokes {@link IPage#detach()}.
*/
public void releasePage(IPage page)
{
Tapestry.clearMethodInvocations();
page.detach();
Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page);
PageKey key = buildKey(page);
try
{
_pool.returnObject(key, page);
} catch (Exception ex)
{
if (RuntimeException.class.isInstance(ex))
throw (RuntimeException)ex;
else
throw new ApplicationRuntimeException(PageloadMessages.errorPagePoolGet(key), ex);
}
}
public void resetEventDidOccur()
{
_pool.clear();
}
public void reportStatus(ReportStatusEvent event)
{
event.title(_serviceId);
event.section("Page Pool");
event.property("active", _pool.getNumActive());
event.property("idle", _pool.getNumIdle());
}
public void setServiceId(String serviceId)
{
_serviceId = serviceId;
}
public void setRequestCycle(IRequestCycle cycle)
{
_cycle = cycle;
}
/** @since 4.0 */
public void setClassResolver(ClassResolver resolver)
{
_classResolver = resolver;
}
/** @since 4.0 */
public void setPageSpecificationResolver(PageSpecificationResolver resolver)
{
_pageSpecificationResolver = resolver;
}
/** @since 4.0 */
public void setLoader(IPageLoader loader)
{
_loader = loader;
}
public void setPropertySource(IPropertySource propertySource)
{
_propertySource = propertySource;
}
}