blob: 0a309801a377d1c8ef53573b8760436a621f85d4 [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.engine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.util.Defense;
import org.apache.hivemind.util.ToStringBuilder;
import org.apache.tapestry.*;
import org.apache.tapestry.listener.ListenerMap;
import org.apache.tapestry.services.ComponentMessagesSource;
import org.apache.tapestry.services.DataSqueezer;
import org.apache.tapestry.services.Infrastructure;
import org.apache.tapestry.services.TemplateSource;
import org.apache.tapestry.spec.IApplicationSpecification;
import org.apache.tapestry.web.WebRequest;
import org.apache.tapestry.web.WebResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Basis for building real Tapestry applications. Immediate subclasses provide different strategies
* for managing page state and other resources between request cycles.
* <p>
* Note: much of this description is <em>in transition</em> as part of Tapestry 4.0. All ad-hoc
* singletons and such are being replaced with HiveMind services.
* <p>
* Uses a shared instance of {@link TemplateSource},{@link ISpecificationSource},
* {@link IScriptSource}and {@link ComponentMessagesSource}stored as attributes of the
* {@link ServletContext}(they will be shared by all sessions).
* <p>
* An engine is designed to be very lightweight. Particularily, it should <b>never </b> hold
* references to any {@link IPage}or {@link org.apache.tapestry.IComponent}objects. The entire
* system is based upon being able to quickly rebuild the state of any page(s).
* <p>
* Where possible, instance variables should be transient.
* <p>
* In practice, a subclass (usually {@link BaseEngine}) is used without subclassing. Instead, a
* visit object is specified. To facilitate this, the application specification may include a
* property, <code>org.apache.tapestry.visit-class</code> which is the class name to instantiate
* when a visit object is first needed.
* <p>
* Some of the classes' behavior is controlled by JVM system properties (typically only used during
* development): <table border=1>
* <tr>
* <th>Property</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>org.apache.tapestry.enable-reset-service</td>
* <td>If true, enabled an additional service, reset, that allow page, specification and template
* caches to be cleared on demand.</td>
* </tr>
* <tr>
* <td>org.apache.tapestry.disable-caching</td>
* <td>If true, then the page, specification, template and script caches will be cleared after each
* request. This slows things down, but ensures that the latest versions of such files are used.
* Care should be taken that the source directories for the files preceeds any versions of the files
* available in JARs or WARs.</td>
* </tr>
* </table>
*
* @author Howard Lewis Ship
*/
public abstract class AbstractEngine implements IEngine
{
/**
* The name of the application specification property used to specify the class of the visit
* object.
*/
public static final String VISIT_CLASS_PROPERTY_NAME = "org.apache.tapestry.visit-class";
private static final Log LOG = LogFactory.getLog(AbstractEngine.class);
/**
* The link to the world of HiveMind services.
*
* @since 4.0
*/
private Infrastructure _infrastructure;
private ListenerMap _listeners;
/**
* The curent locale for the engine, which may be changed at any time.
*/
private Locale _locale;
/**
* @see org.apache.tapestry.error.ExceptionPresenter
*/
protected void activateExceptionPage(IRequestCycle cycle, Throwable cause)
{
_infrastructure.getExceptionPresenter().presentException(cycle, cause);
}
/**
* Writes a detailed report of the exception to <code>System.err</code>.
*
* @see org.apache.tapestry.error.RequestExceptionReporter
*/
public void reportException(String reportTitle, Throwable ex)
{
_infrastructure.getRequestExceptionReporter().reportRequestException(reportTitle, ex);
}
/**
* Invoked at the end of the request cycle to release any resources specific to the request
* cycle. This implementation does nothing and may be overriden freely.
*/
protected void cleanupAfterRequest(IRequestCycle cycle)
{
}
/**
* Returns the locale for the engine. This is initially set by the {@link ApplicationServlet}
* but may be updated by the application.
*/
public Locale getLocale()
{
return _locale;
}
/**
* Returns a service with the given name.
*
* @see Infrastructure#getServiceMap()
* @see org.apache.tapestry.services.ServiceMap
*/
public IEngineService getService(String name)
{
return _infrastructure.getServiceMap().getService(name);
}
/** @see Infrastructure#getApplicationSpecification() */
public IApplicationSpecification getSpecification()
{
return _infrastructure.getApplicationSpecification();
}
/** @see Infrastructure#getSpecificationSource() */
public ISpecificationSource getSpecificationSource()
{
return _infrastructure.getSpecificationSource();
}
/**
* Invoked, typically, when an exception occurs while servicing the request. This method resets
* the output, sets the new page and renders it.
*/
protected void redirect(String pageName, IRequestCycle cycle,
ApplicationRuntimeException exception)
throws IOException
{
IPage page = cycle.getPage(pageName);
cycle.activate(page);
renderResponse(cycle);
}
/**
* Delegates to
* {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}.
*/
public void renderResponse(IRequestCycle cycle)
throws IOException
{
_infrastructure.getResponseRenderer().renderResponse(cycle);
}
/**
* Delegate method for the servlet. Services the request.
*/
public void service(WebRequest request, WebResponse response)
throws IOException
{
IRequestCycle cycle = null;
IEngineService service = null;
if (_infrastructure == null)
_infrastructure = (Infrastructure) request.getAttribute(Constants.INFRASTRUCTURE_KEY);
// Create the request cycle; if this fails, there's not much that can be done ... everything
// else in Tapestry relies on the RequestCycle.
try
{
cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this);
}
catch (RuntimeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new IOException(ex.getMessage());
}
try
{
try
{
service = cycle.getService();
// Let the service handle the rest of the request.
service.service(cycle);
}
catch (PageRedirectException ex)
{
handlePageRedirectException(cycle, ex);
}
catch (RedirectException ex)
{
handleRedirectException(cycle, ex);
}
catch (StaleLinkException ex)
{
handleStaleLinkException(cycle, ex);
}
catch (StaleSessionException ex)
{
handleStaleSessionException(cycle, ex);
}
}
catch (Exception ex)
{
// Attempt to switch to the exception page. However, this may itself
// fail for a number of reasons, in which case an ApplicationRuntimeException is
// thrown.
if (LOG.isDebugEnabled())
LOG.debug("Uncaught exception", ex);
activateExceptionPage(cycle, ex);
}
finally
{
try
{
cycle.cleanup();
_infrastructure.getApplicationStateManager().flush();
}
catch (Exception ex)
{
reportException(EngineMessages.exceptionDuringCleanup(ex), ex);
}
}
}
/**
* Handles {@link PageRedirectException} which involves executing
* {@link IRequestCycle#activate(IPage)} on the target page (of the exception), until either a
* loop is found, or a page succesfully activates.
* <p>
* This should generally not be overriden in subclasses.
*
* @since 3.0
*/
protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception)
throws IOException
{
List pageNames = new ArrayList();
String pageName = exception.getTargetPageName();
while (true)
{
if (pageNames.contains(pageName))
{
pageNames.add(pageName);
throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames));
}
// Record that this page has been a target.
pageNames.add(pageName);
try
{
// Attempt to activate the new page.
cycle.activate(pageName);
break;
}
catch (PageRedirectException secondRedirectException)
{
pageName = secondRedirectException.getTargetPageName();
}
}
renderResponse(cycle);
}
/**
* Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleLinkException} is
* thrown by the {@link IEngineService service}. This implementation sets the message property
* of the StaleLink page to the message provided in the exception, then invokes
* {@link #redirect(String, IRequestCycle, ApplicationRuntimeException)} to render the StaleLink
* page.
* <p>
* Subclasses may overide this method (without invoking this implementation). A better practice
* is to contribute an alternative implementation of
* {@link org.apache.tapestry.error.StaleLinkExceptionPresenter} to the
* tapestry.InfrastructureOverrides configuration point.
* <p>
* A common practice is to present an error message on the application's Home page. Alternately,
* the application may provide its own version of the StaleLink page, overriding the framework's
* implementation (probably a good idea, because the default page hints at "application errors"
* and isn't localized). The overriding StaleLink implementation must implement a message
* property of type String.
*
* @since 0.2.10
*/
protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception)
throws IOException
{
_infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception);
}
/**
* Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleSessionException} is
* thrown by the {@link IEngineService service}. This implementation uses the
* {@link org.apache.tapestry.error.StaleSessionExceptionPresenter} to render the StaleSession
* page.
* <p>
* Subclasses may overide this method (without invoking this implementation), but it is better
* to override the tapestry.error.StaleSessionExceptionReporter service instead (or contribute a
* replacement to the tapestry.InfrastructureOverrides configuration point).
*
* @since 0.2.10
*/
protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception)
throws IOException
{
_infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception);
}
/**
* Changes the locale for the engine.
*/
public void setLocale(Locale value)
{
Defense.notNull(value, "locale");
_locale = value;
// The locale may be set before the engine is initialized with the Infrastructure.
if (_infrastructure != null)
_infrastructure.setLocale(value);
}
/**
* @see Infrastructure#getClassResolver()
*/
public ClassResolver getClassResolver()
{
return _infrastructure.getClassResolver();
}
/**
* {@inheritDoc}
*/
public String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("locale", _locale);
return builder.toString();
}
/**
* Gets the visit object from the
* {@link org.apache.tapestry.engine.state.ApplicationStateManager}, creating it if it does not
* already exist.
* <p>
* As of Tapestry 4.0, this will always create the visit object, possibly creating a new session
* in the process.
*/
public Object getVisit()
{
return _infrastructure.getApplicationStateManager().get("visit");
}
public void setVisit(Object visit)
{
_infrastructure.getApplicationStateManager().store("visit", visit);
}
/**
* Gets the visit object from the
* {@link org.apache.tapestry.engine.state.ApplicationStateManager}, which will create it as
* necessary.
*/
public Object getVisit(IRequestCycle cycle)
{
return getVisit();
}
public boolean getHasVisit()
{
return _infrastructure.getApplicationStateManager().exists("visit");
}
public IScriptSource getScriptSource()
{
return _infrastructure.getScriptSource();
}
/**
* Allows subclasses to include listener methods easily.
*
* @since 1.0.2
*/
public ListenerMap getListeners()
{
if (_listeners == null)
_listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this);
return _listeners;
}
/**
* Invoked when a {@link RedirectException} is thrown during the processing of a request.
*
* @throws ApplicationRuntimeException
* if an {@link IOException},{@link ServletException}is thrown by the redirect,
* or if no {@link javax.servlet.RequestDispatcher} can be found for local resource.
* @since 2.2
*/
protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException)
{
String location = redirectException.getRedirectLocation();
if (LOG.isDebugEnabled())
LOG.debug("Redirecting to: " + location);
_infrastructure.getRequest().forward(location);
}
/**
* @see Infrastructure#getDataSqueezer()
*/
public DataSqueezer getDataSqueezer()
{
return _infrastructure.getDataSqueezer();
}
/** @since 2.3 */
public IPropertySource getPropertySource()
{
return _infrastructure.getApplicationPropertySource();
}
/** @since 4.0 */
public Infrastructure getInfrastructure()
{
return _infrastructure;
}
public String getOutputEncoding()
{
return _infrastructure.getOutputEncoding();
}
}