blob: 4a291954dec306b5ec2ab6f3db900f50ad745e8b [file] [log] [blame]
// Copyright 2004, 2008 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.bsf.BSFManager;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.*;
import org.apache.tapestry.asset.ResourceChecksumSource;
import org.apache.tapestry.asset.ResourceChecksumSourceImpl;
import org.apache.tapestry.enhance.DefaultComponentClassEnhancer;
import org.apache.tapestry.enhance.IEnhancedClassFactory;
import org.apache.tapestry.enhance.javassist.EnhancedClassFactory;
import org.apache.tapestry.listener.ListenerMap;
import org.apache.tapestry.pageload.PageSource;
import org.apache.tapestry.request.RequestContext;
import org.apache.tapestry.request.ResponseOutputStream;
import org.apache.tapestry.spec.IApplicationSpecification;
import org.apache.tapestry.util.*;
import org.apache.tapestry.util.exception.ExceptionAnalyzer;
import org.apache.tapestry.util.io.DataSqueezer;
import org.apache.tapestry.util.pool.Pool;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
/**
* Basis for building real Tapestry applications. Immediate subclasses provide different strategies for managing page
* state and other resources between request cycles.
* <p/>
* Uses a shared instance of {@link ITemplateSource}, {@link ISpecificationSource}, {@link IScriptSource} and {@link
* IComponentMessagesSource} stored as attributes of the {@link ServletContext} (they will be shared by all sessions).
* <p/>
* <p>An application 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/>
* <p>Where possible, instance variables should be transient. They can be restored inside {@link
* #setupForRequest(RequestContext)}.
* <p/>
* <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. See {@link #createVisit(IRequestCycle)} for more details.
* <p/>
* <p>Some of the classes' behavior is controlled by JVM system properties (typically only used during development):
* <p/>
* <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. See {@link #isResetServiceEnabled()}. </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
* @version $Id$
*/
public abstract class AbstractEngine
implements IEngine, IEngineServiceView, Externalizable, HttpSessionBindingListener
{
private static final Log LOG = LogFactory.getLog(AbstractEngine.class);
/**
* @since 2.0.4
*/
private static final long serialVersionUID = 6884834397673817117L;
private transient String _contextPath;
private transient String _servletPath;
private transient String _clientAddress;
private transient String _sessionId;
private transient boolean _stateful;
private transient ListenerMap _listeners;
/**
* @since 2.2 *
*/
private transient DataSqueezer _dataSqueezer;
/**
* An object used to contain application-specific server side state.
*/
private Object _visit;
/**
* The globally shared application object. Typically, this is created when first needed, shared between sessions
* and engines, and stored in the {@link ServletContext}.
*
* @since 2.3
*/
private transient Object _global;
/**
* The base name for the servlet context key used to store the application-defined Global object, if any.
*
* @since 2.3
*/
public static final String GLOBAL_NAME = "org.apache.tapestry.global";
/**
* The name of the application property that will be used to determine the encoding to use when generating the
* output
*
* @since 3.0
*/
public static final String OUTPUT_ENCODING_PROPERTY_NAME =
"org.apache.tapestry.output-encoding";
/**
* The default encoding that will be used when generating the output. It is used if no output encoding property has
* been specified.
*
* @since 3.0
*/
public static final String DEFAULT_OUTPUT_ENCODING = "UTF-8";
/**
* The curent locale for the engine, which may be changed at any time.
*/
private Locale _locale;
/**
* Set by {@link #setLocale(Locale)} when the locale is changed; this allows the locale cookie to be updated.
*/
private boolean _localeChanged;
/**
* The specification for the application, which lives in the {@link ServletContext}. If the session (and
* application) moves to a different context (i.e., a different JVM), then we want to reconnect to the specification
* in the new context. A check is made on every request cycle as needed.
*/
protected transient IApplicationSpecification _specification;
/**
* The source for template data. The template source is stored in the {@link ServletContext} as a named attribute.
* After de-serialization, the application can re-connect to the template source (or create a new one).
*/
protected transient ITemplateSource _templateSource;
/**
* The source for component specifications, stored in the {@link ServletContext} (like {@link #_templateSource}).
*/
protected transient ISpecificationSource _specificationSource;
/**
* The source for parsed scripts, again, stored in the {@link ServletContext}.
*
* @since 1.0.2
*/
private transient IScriptSource _scriptSource;
/**
* The name of the context attribute for the {@link IScriptSource} instance. The application's name is appended.
*
* @since 1.0.2
*/
protected static final String SCRIPT_SOURCE_NAME = "org.apache.tapestry.ScriptSource";
/**
* The name of the context attribute for the {@link IComponentMessagesSource} instance. The application's name is
* appended.
*
* @since 2.0.4
*/
protected static final String STRINGS_SOURCE_NAME = "org.apache.tapestry.StringsSource";
private transient IComponentMessagesSource _stringsSource;
/**
* 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";
/**
* Servlet context attribute name for the default {@link ITemplateSource} instance. The application's name is
* appended.
*/
protected static final String TEMPLATE_SOURCE_NAME = "org.apache.tapestry.TemplateSource";
/**
* Servlet context attribute name for the default {@link ISpecificationSource} instance. The application's name is
* appended.
*/
protected static final String SPECIFICATION_SOURCE_NAME =
"org.apache.tapestry.SpecificationSource";
/**
* Servlet context attribute name for the {@link IPageSource} instance. The application's name is appended.
*/
protected static final String PAGE_SOURCE_NAME = "org.apache.tapestry.PageSource";
/**
* Servlet context attribute name for a shared instance of {@link DataSqueezer}. The instance is actually shared
* between Tapestry applications within the same context (which will have the same ClassLoader).
*
* @since 2.2
*/
protected static final String DATA_SQUEEZER_NAME = "org.apache.tapestry.DataSqueezer";
/**
* Servlet context attribute name for a shared instance of {@link ResourceChecksumSource}.
*
* @since 3.0.3
*/
protected static final String RESOURCE_CHECKSUM_SOURCE_NAME =
"org.apache.tapestry.ResourceChecksumSource";
/**
* The source for pages, which acts as a pool, but is capable of creating pages as needed. Stored in the {@link
* ServletContext}, like {@link #_templateSource}.
*/
private transient IPageSource _pageSource;
/**
* If true (set from JVM system parameter <code>org.apache.tapestry.enable-reset-service</code>) then the reset
* service will be enabled, allowing the cache of pages, specifications and template to be cleared on demand.
*/
private static final boolean _resetServiceEnabled =
Boolean.getBoolean("org.apache.tapestry.enable-reset-service");
/**
* If true (set from the JVM system parameter <code>org.apache.tapestry.disable-caching</code>) then the cache of
* pages, specifications and template will be cleared after each request.
*/
private static final boolean _disableCaching =
Boolean.getBoolean("org.apache.tapestry.disable-caching");
private transient IResourceResolver _resolver;
/**
* Constant used to store a {@link org.apache.tapestry.util.IPropertyHolder} in the servlet context.
*
* @since 2.3
*/
protected static final String PROPERTY_SOURCE_NAME = "org.apache.tapestry.PropertySource";
/**
* A shared instance of {@link IPropertySource}
*
* @see #createPropertySource(RequestContext)
* @since 3.0
*/
private transient IPropertySource _propertySource;
/**
* Map from service name to service instance.
*
* @since 1.0.9
*/
private transient Map _serviceMap;
protected static final String SERVICE_MAP_NAME = "org.apache.tapestry.ServiceMap";
/**
* A shared instance of {@link Pool}.
*
* @see #createPool(RequestContext)
* @since 3.0
*/
private transient Pool _pool;
protected static final String POOL_NAME = "org.apache.tapestry.Pool";
/**
* Name of a shared instance of {@link org.apache.tapestry.engine.IComponentClassEnhancer} stored in the {@link
* ServletContext}.
*
* @since 3.0
*/
protected static final String ENHANCER_NAME = "org.apache.tapestry.ComponentClassEnhancer";
/**
* A shared instance of {@link org.apache.tapestry.engine.IComponentClassEnhancer}.
*
* @see #createComponentClassEnhancer(RequestContext)
* @since 3.0
*/
private transient IComponentClassEnhancer _enhancer;
protected static final String CLASS_FACTORY_NAME = "org.apache.tapestry.enhance.javassist.EnhancedClassFactory";
private transient IEnhancedClassFactory _classFactory;
/**
* Set to true when there is a (potential) change to the internal state of the engine, set to false when the engine
* is stored into the {@link HttpSession}.
*
* @since 3.0
*/
private transient boolean _dirty;
/**
* The instance of {@link IMonitorFactory} used to create a monitor.
*
* @since 3.0
*/
private transient IMonitorFactory _monitorFactory;
/**
* Used to obtain resource checksums for the asset service.
*
* @since 3.0.3
*/
private transient ResourceChecksumSource _resourceChecksumSource;
/**
* The name of the context attribute for the {@link ExpressionEvaluator} instance. The application's name is
* appended.
*/
protected static final String EXPRESSION_EVALUATOR_NAME = "org.apache.tapestry.engine.ExpressionEvaluator";
private transient ExpressionEvaluator _expressionEvaluator;
private transient ExpressionCache _expressionCache;
private transient String _outputEncoding;
/**
* Sets the Exception page's exception property, then renders the Exception page.
* <p/>
* <p>If the render throws an exception, then copious output is sent to <code>System.err</code> and a {@link
* ServletException} is thrown.
*/
protected void activateExceptionPage(
IRequestCycle cycle,
ResponseOutputStream output,
Throwable cause)
throws ServletException
{
try
{
IPage exceptionPage = cycle.getPage(getExceptionPageName());
exceptionPage.setProperty("exception", cause);
cycle.activate(exceptionPage);
renderResponse(cycle, output);
}
catch (Throwable ex)
{
// Worst case scenario. The exception page itself is broken, leaving
// us with no option but to write the cause to the output.
reportException(
Tapestry.getMessage("AbstractEngine.unable-to-process-client-request"),
cause);
// Also, write the exception thrown when redendering the exception
// page, so that can get fixed as well.
reportException(
Tapestry.getMessage("AbstractEngine.unable-to-present-exception-page"),
ex);
// And throw the exception.
throw new ServletException(ex.getMessage(), ex);
}
}
/**
* Writes a detailed report of the exception to <code>System.err</code>.
*/
public void reportException(String reportTitle, Throwable ex)
{
LOG.warn(reportTitle, ex);
System.err.println("\n\n**********************************************************\n\n");
System.err.println(reportTitle);
System.err.println(
"\n\n Session id: "
+ _sessionId
+ "\n Client address: "
+ _clientAddress
+ "\n\nExceptions:\n");
new ExceptionAnalyzer().reportException(ex, System.err);
System.err.println("\n**********************************************************\n");
}
/**
* Invoked at the end of the request cycle to release any resources specific to the request cycle.
*/
protected abstract void cleanupAfterRequest(IRequestCycle cycle);
/**
* Extends the description of the class generated by {@link #toString()}. If a subclass adds additional instance
* variables that should be described in the instance description, it may overide this method. This implementation
* does nothing.
*
* @see #toString()
*/
protected void extendDescription(ToStringBuilder builder)
{
}
/**
* 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;
}
/**
* Overriden in subclasses that support monitoring. Should create and return an instance of {@link IMonitor} that
* is appropriate for the request cycle described by the {@link RequestContext}.
* <p/>
* <p>The monitor is used to create a {@link RequestCycle}.
* <p/>
* <p>This implementation uses a {@link IMonitorFactory} to create the monitor instance. The factory is provided as
* an application extension. If the application extension does not exist, {@link DefaultMonitorFactory} is used.
* <p/>
* <p>As of release 3.0, this method should <em>not</em> return null.
*/
public IMonitor getMonitor(RequestContext context)
{
if (_monitorFactory == null)
{
if (_specification.checkExtension(Tapestry.MONITOR_FACTORY_EXTENSION_NAME))
_monitorFactory =
(IMonitorFactory) _specification.getExtension(
Tapestry.MONITOR_FACTORY_EXTENSION_NAME,
IMonitorFactory.class);
else
_monitorFactory = DefaultMonitorFactory.SHARED;
}
return _monitorFactory.createMonitor(context);
}
public IPageSource getPageSource()
{
return _pageSource;
}
/**
* Returns a service with the given name. Services are created by the first call to {@link
* #setupForRequest(RequestContext)}.
*/
public IEngineService getService(String name)
{
IEngineService result = (IEngineService) _serviceMap.get(name);
if (result == null)
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unknown-service", name));
return result;
}
public String getServletPath()
{
return _servletPath;
}
/**
* Returns the context path, the prefix to apply to any URLs so that they are recognized as belonging to the Servlet
* 2.2 context.
*
* @see org.apache.tapestry.asset.ContextAsset
*/
public String getContextPath()
{
return _contextPath;
}
/**
* Returns the specification, if available, or null otherwise.
* <p/>
* <p>To facilitate deployment across multiple servlet containers, the application is serializable. However, the
* reference to the specification is transient. When an application instance is deserialized, it reconnects with
* the application specification by locating it in the {@link ServletContext} or parsing it fresh.
*/
public IApplicationSpecification getSpecification()
{
return _specification;
}
public ISpecificationSource getSpecificationSource()
{
return _specificationSource;
}
public ITemplateSource getTemplateSource()
{
return _templateSource;
}
/**
* Reads the state serialized by {@link #writeExternal(ObjectOutput)}.
* <p/>
* <p>This always set the stateful flag. By default, a deserialized session is stateful (else, it would not have
* been serialized).
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
_stateful = true;
String localeName = in.readUTF();
_locale = Tapestry.getLocale(localeName);
_visit = in.readObject();
}
/**
* Writes the following properties:
* <p/>
* <ul> <li>locale name ({@link Locale#toString()}) <li>visit </ul>
*/
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeUTF(_locale.toString());
out.writeObject(_visit);
}
/**
* 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,
ResponseOutputStream out,
ApplicationRuntimeException exception)
throws IOException, ServletException
{
// Discard any output from the previous page.
out.reset();
IPage page = cycle.getPage(pageName);
cycle.activate(page);
renderResponse(cycle, out);
}
public void renderResponse(IRequestCycle cycle, ResponseOutputStream output)
throws ServletException, IOException
{
if (LOG.isDebugEnabled())
LOG.debug("Begin render response.");
// If the locale has changed during this request cycle then
// do the work to propogate the locale change into
// subsequent request cycles.
if (_localeChanged)
{
_localeChanged = false;
RequestContext context = cycle.getRequestContext();
ApplicationServlet servlet = context.getServlet();
servlet.writeLocaleCookie(_locale, this, context);
}
// Commit all changes and ignore further changes.
IPage page = cycle.getPage();
IMarkupWriter writer = page.getResponseWriter(output);
output.setContentType(writer.getContentType());
boolean discard = true;
try
{
cycle.renderPage(writer);
discard = false;
}
finally
{
// Closing the writer closes its PrintWriter and a whole stack of java.io objects,
// which tend to stream a lot of output that eventually hits the
// ResponseOutputStream. If we are discarding output anyway (due to an exception
// getting thrown during the render), we can save ourselves some trouble
// by ignoring it.
if (discard)
output.setDiscard(true);
writer.close();
if (discard)
output.setDiscard(false);
}
}
/**
* Invalidates the session, then redirects the client web browser to the servlet's prefix, starting a new visit.
* <p/>
* <p>Subclasses should perform their own restart (if necessary, which is rarely) before invoking this
* implementation.
*/
public void restart(IRequestCycle cycle) throws IOException
{
RequestContext context = cycle.getRequestContext();
HttpSession session = context.getSession();
if (session != null)
{
try
{
session.invalidate();
}
catch (IllegalStateException ex)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception thrown invalidating HttpSession.", ex);
// Otherwise, ignore it.
}
}
// Make isStateful() return false, so that the servlet doesn't
// try to store the engine back into the (now invalid) session.
_stateful = false;
String url = context.getAbsoluteURL(_servletPath);
context.redirect(url);
}
/**
* Delegate method for the servlet. Services the request.
*/
public boolean service(RequestContext context) throws ServletException, IOException
{
ApplicationServlet servlet = context.getServlet();
IRequestCycle cycle = null;
ResponseOutputStream output = null;
IMonitor monitor = null;
if (LOG.isDebugEnabled())
LOG.debug("Begin service " + context.getRequestURI());
if (_specification == null)
_specification = servlet.getApplicationSpecification();
// The servlet invokes setLocale() before invoking service(). We want
// to ignore that setLocale() ... that is, not force a cookie to be
// written.
_localeChanged = false;
if (_resolver == null)
_resolver = servlet.getResourceResolver();
try
{
setupForRequest(context);
monitor = getMonitor(context);
output = new ResponseOutputStream(context.getResponse());
}
catch (Exception ex)
{
reportException(Tapestry.getMessage("AbstractEngine.unable-to-begin-request"), ex);
throw new ServletException(ex.getMessage(), ex);
}
IEngineService service = null;
try
{
try
{
String serviceName;
try
{
serviceName = extractServiceName(context);
if (Tapestry.isBlank(serviceName))
serviceName = Tapestry.HOME_SERVICE;
// Must have a service to create the request cycle.
// Must have a request cycle to report an exception.
service = getService(serviceName);
}
catch (Exception ex)
{
service = getService(Tapestry.HOME_SERVICE);
cycle = createRequestCycle(context, service, monitor);
throw ex;
}
cycle = createRequestCycle(context, service, monitor);
monitor.serviceBegin(serviceName, context.getRequestURI());
// Invoke the service, which returns true if it may have changed
// the state of the engine (most do return true).
service.service(this, cycle, output);
// Return true only if the engine is actually dirty. This cuts down
// on the number of times the engine is stored into the
// session unceccesarily.
return _dirty;
}
catch (PageRedirectException ex)
{
handlePageRedirectException(ex, cycle, output);
}
catch (RedirectException ex)
{
handleRedirectException(cycle, ex);
}
catch (StaleLinkException ex)
{
handleStaleLinkException(ex, cycle, output);
}
catch (StaleSessionException ex)
{
handleStaleSessionException(ex, cycle, output);
}
}
catch (Exception ex)
{
monitor.serviceException(ex);
// Discard any output (if possible). If output has already been sent to
// the client, then things get dicey. Note that this block
// gets activated if the StaleLink or StaleSession pages throws
// any kind of exception.
// Attempt to switch to the exception page. However, this may itself fail
// for a number of reasons, in which case a ServletException is thrown.
output.reset();
if (LOG.isDebugEnabled())
LOG.debug("Uncaught exception", ex);
activateExceptionPage(cycle, output, ex);
}
finally
{
if (service != null)
monitor.serviceEnd(service.getName());
try
{
cycle.cleanup();
// Closing the buffered output closes the underlying stream as well.
if (output != null)
output.forceFlush();
}
catch (Exception ex)
{
reportException(Tapestry.getMessage("AbstractEngine.exception-during-cleanup"), ex);
}
finally
{
cleanupAfterRequest(cycle);
}
if (_disableCaching)
{
try
{
clearCachedData();
}
catch (Exception ex)
{
reportException(
Tapestry.getMessage("AbstractEngine.exception-during-cache-clear"),
ex);
}
}
if (LOG.isDebugEnabled())
LOG.debug("End service");
}
return _dirty;
}
/**
* Handles {@link PageRedirectException} which involves executing {@link IPage#validate(IRequestCycle)} on the
* target page (of the exception), until either a loop is found, or a page succesfully validates and can be
* activated.
* <p/>
* <p>This should generally not be overriden in subclasses.
*
* @since 3.0
*/
protected void handlePageRedirectException(
PageRedirectException ex,
IRequestCycle cycle,
ResponseOutputStream output)
throws IOException, ServletException
{
List pageNames = new ArrayList();
String pageName = ex.getTargetPageName();
while (true)
{
if (pageNames.contains(pageName))
{
// Add the offending page to pageNames so it shows in the
// list.
pageNames.add(pageName);
StringBuffer buffer = new StringBuffer();
int count = pageNames.size();
for (int i = 0; i < count; i++)
{
if (i > 0)
buffer.append("; ");
buffer.append(pageNames.get(i));
}
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.validate-cycle", buffer.toString()));
}
// Record that this page has been a target.
pageNames.add(pageName);
try
{
// Attempt to activate the new page.
cycle.activate(pageName);
break;
}
catch (PageRedirectException ex2)
{
pageName = ex2.getTargetPageName();
}
}
// Discard any output from the previous page.
output.reset();
renderResponse(cycle, output);
}
/**
* Invoked from {@link #service(RequestContext)} to create an instance of {@link IRequestCycle} for the current
* request. This implementation creates an returns an instance of {@link RequestCycle}.
*
* @since 3.0
*/
protected IRequestCycle createRequestCycle(
RequestContext context,
IEngineService service,
IMonitor monitor)
{
return new RequestCycle(this, context, service, monitor);
}
/**
* Invoked by {@link #service(RequestContext)} 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, ResponseOutputStream,
* ApplicationRuntimeException)} to render the StaleLink page.
* <p/>
* <p>Subclasses may overide this method (without invoking this implementation). A common practice is to present an
* error message on the application's Home page.
* <p/>
* <p>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(
StaleLinkException ex,
IRequestCycle cycle,
ResponseOutputStream output)
throws IOException, ServletException
{
String staleLinkPageName = getStaleLinkPageName();
IPage page = cycle.getPage(staleLinkPageName);
page.setProperty("message", ex.getMessage());
redirect(staleLinkPageName, cycle, output, ex);
}
/**
* Invoked by {@link #service(RequestContext)} if a {@link StaleSessionException} is thrown by the {@link
* IEngineService service}. This implementation invokes {@link #redirect(String, IRequestCycle,
* ResponseOutputStream, ApplicationRuntimeException)} to render the StaleSession page.
* <p/>
* <p>Subclasses may overide this method (without invoking this implementation). A common practice is to present an
* eror message on the application's Home page.
*
* @since 0.2.10
*/
protected void handleStaleSessionException(
StaleSessionException ex,
IRequestCycle cycle,
ResponseOutputStream output)
throws IOException, ServletException
{
redirect(getStaleSessionPageName(), cycle, output, ex);
}
/**
* Discards all cached pages, component specifications and templates. Subclasses who override this method should
* invoke this implementation as well.
*
* @since 1.0.1
*/
public void clearCachedData()
{
_pool.clear();
_pageSource.reset();
_specificationSource.reset();
_templateSource.reset();
_scriptSource.reset();
_stringsSource.reset();
_enhancer.reset();
_resourceChecksumSource.reset();
_expressionCache.reset();
_expressionEvaluator.reset();
_outputEncoding = null;
}
/**
* Changes the locale for the engine.
*/
public void setLocale(Locale value)
{
if (value == null)
throw new IllegalArgumentException("May not change engine locale to null.");
// Because locale changes are expensive (it involves writing a cookie and all that),
// we're careful not to really change unless there's a true change in value.
if (!value.equals(_locale))
{
_locale = value;
_localeChanged = true;
markDirty();
}
}
/**
* Invoked from {@link #service(RequestContext)} to ensure that the engine's instance variables are setup. This
* allows the application a chance to restore transient variables that will not have survived deserialization.
* <p/>
* Determines the servlet prefix: this is the base URL used by {@link IEngineService services} to build URLs. It
* consists of two parts: the context path and the servlet path.
* <p/>
* <p>The servlet path is retrieved from {@link HttpServletRequest#getServletPath()}.
* <p/>
* <p>The context path is retrieved from {@link HttpServletRequest#getContextPath()}.
* <p/>
* <p>The global object is retrieved from {@link IEngine#getGlobal()} method.
* <p/>
* <p>The final path is available via the {@link #getServletPath()} method.
* <p/>
* <p>In addition, this method locates and/or creates the: <ul> <li>{@link IComponentClassEnhancer} <li>{@link Pool}
* <li>{@link ITemplateSource} <li>{@link ISpecificationSource} <li>{@link IPageSource} <li>{@link IEngineService}
* {@link Map} <ll>{@link IScriptSource} <li>{@link IComponentMessagesSource} <li>{@link IPropertySource} </ul>
* <p/>
* <p>This order is important, because some of the later shared objects depend on some of the earlier shared objects
* already having been located or created (especially {@link #getPool() pool}).
* <p/>
* <p>Subclasses should invoke this implementation first, then perform their own setup. They must also synchronize
* on the {@link org.apache.tapestry.request.RequestContext#getServlet() application servlet}.
*/
protected void setupForRequest(RequestContext context)
{
HttpServlet servlet = context.getServlet();
ServletContext servletContext = servlet.getServletContext();
HttpServletRequest request = context.getRequest();
HttpSession session = context.getSession();
if (session != null)
_sessionId = context.getSession().getId();
else
_sessionId = null;
// Previously, this used getRemoteHost(), but that requires an
// expensive reverse DNS lookup. Possibly, the host name lookup
// should occur ... but only if there's an actual error message
// to display.
if (_clientAddress == null)
_clientAddress = request.getRemoteAddr();
// servletPath is null, so this means either we're doing the
// first request in this session, or we're handling a subsequent
// request in another JVM (i.e. another server in the cluster).
// In any case, we have to do some late (re-)initialization.
if (_servletPath == null)
{
// Get the path *within* the servlet context
// In rare cases related to the tagsupport service, getServletPath() is wrong
// (its a JSP, which invokes Tapestry as an include, thus muddling what
// the real servlet and servlet path is). In those cases, the JSP tag
// will inform us.
String path =
(String) request.getAttribute(Tapestry.TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE);
if (path == null)
path = request.getServletPath();
// Get the context path, which may be the empty string
// (but won't be null).
_contextPath = request.getContextPath();
_servletPath = _contextPath + path;
}
String servletName = context.getServlet().getServletName();
// Synchronize on the servlet to make sure that two different IEngine instances
// do not try ot create some of the common dependencies simulataneously. We enclose
// all the code that uses the ServletContext as a cache. This is the motivation for
// adding an IoC container to Tapestry 4.
// Note: by borrowing ConcurrentBarrier from Tapestry 5, we could perhaps optimize this
// a bit ... but in most cases, we'll "flit" into the synchronized block, see non-nulls
// for all the fields, and "flit" back out with virtually no chance of contention.
synchronized (servlet)
{
if (_propertySource == null)
{
String name = PROPERTY_SOURCE_NAME + ":" + servletName;
_propertySource = (IPropertySource) servletContext.getAttribute(name);
if (_propertySource == null)
{
_propertySource = createPropertySource(context);
servletContext.setAttribute(name, _propertySource);
}
}
if (_classFactory == null)
{
String name = CLASS_FACTORY_NAME + ":" + servletName;
_classFactory = (IEnhancedClassFactory) servletContext.getAttribute(name);
if (_classFactory == null)
{
_classFactory = createClassFactory();
servletContext.setAttribute(name, _classFactory);
}
}
if (_enhancer == null)
{
String name = ENHANCER_NAME + ":" + servletName;
_enhancer = (IComponentClassEnhancer) servletContext.getAttribute(name);
if (_enhancer == null)
{
_enhancer = createComponentClassEnhancer(context);
servletContext.setAttribute(name, _enhancer);
}
}
if (_pool == null)
{
String name = POOL_NAME + ":" + servletName;
_pool = (Pool) servletContext.getAttribute(name);
_pool = createPool(context);
}
if (_templateSource == null)
{
String name = TEMPLATE_SOURCE_NAME + ":" + servletName;
_templateSource = (ITemplateSource) servletContext.getAttribute(name);
if (_templateSource == null)
{
_templateSource = createTemplateSource(context);
servletContext.setAttribute(name, _templateSource);
}
}
if (_specificationSource == null)
{
String name = SPECIFICATION_SOURCE_NAME + ":" + servletName;
_specificationSource = (ISpecificationSource) servletContext.getAttribute(name);
if (_specificationSource == null)
{
_specificationSource = createSpecificationSource(context);
servletContext.setAttribute(name, _specificationSource);
}
}
if (_pageSource == null)
{
String name = PAGE_SOURCE_NAME + ":" + servletName;
_pageSource = (IPageSource) servletContext.getAttribute(name);
if (_pageSource == null)
{
_pageSource = createPageSource(context);
servletContext.setAttribute(name, _pageSource);
}
}
if (_scriptSource == null)
{
String name = SCRIPT_SOURCE_NAME + ":" + servletName;
_scriptSource = (IScriptSource) servletContext.getAttribute(name);
if (_scriptSource == null)
{
_scriptSource = createScriptSource(context);
servletContext.setAttribute(name, _scriptSource);
}
}
if (_serviceMap == null)
{
String name = SERVICE_MAP_NAME + ":" + servletName;
_serviceMap = (Map) servletContext.getAttribute(name);
if (_serviceMap == null)
{
_serviceMap = createServiceMap();
servletContext.setAttribute(name, _serviceMap);
}
}
if (_stringsSource == null)
{
String name = STRINGS_SOURCE_NAME + ":" + servletName;
_stringsSource = (IComponentMessagesSource) servletContext.getAttribute(name);
if (_stringsSource == null)
{
_stringsSource = createComponentStringsSource(context);
servletContext.setAttribute(name, _stringsSource);
}
}
if (_dataSqueezer == null)
{
String name = DATA_SQUEEZER_NAME + ":" + servletName;
_dataSqueezer = (DataSqueezer) servletContext.getAttribute(name);
if (_dataSqueezer == null)
{
_dataSqueezer = createDataSqueezer();
servletContext.setAttribute(name, _dataSqueezer);
}
}
if (_global == null)
{
String name = GLOBAL_NAME + ":" + servletName;
_global = servletContext.getAttribute(name);
if (_global == null)
{
_global = createGlobal(context);
servletContext.setAttribute(name, _global);
}
}
if (_resourceChecksumSource == null)
{
String name = RESOURCE_CHECKSUM_SOURCE_NAME + ":" + servletName;
_resourceChecksumSource = (ResourceChecksumSource) servletContext.getAttribute(name);
if (_resourceChecksumSource == null)
{
_resourceChecksumSource = createResourceChecksumSource();
servletContext.setAttribute(name, _resourceChecksumSource);
}
}
if (_expressionEvaluator == null)
{
String name = EXPRESSION_EVALUATOR_NAME + ":" + servletName;
_expressionEvaluator = (ExpressionEvaluator) servletContext.getAttribute(name);
if (_expressionEvaluator == null)
{
_expressionEvaluator = createExpressionEvaluator();
servletContext.setAttribute(name, _expressionEvaluator);
}
}
// That's the end of reading to and from the servlet context, so we can leave the block
// synchronized against the servlet.
}
String encoding = request.getCharacterEncoding();
if (encoding == null)
{
encoding = getOutputEncoding();
try
{
request.setCharacterEncoding(encoding);
}
catch (UnsupportedEncodingException e)
{
throw new IllegalArgumentException(Tapestry.format("illegal-encoding", encoding));
}
catch (NoSuchMethodError e)
{
// Servlet API 2.2 compatibility
// Behave okay if the setCharacterEncoding() method is unavailable
}
catch (AbstractMethodError e)
{
// Servlet API 2.2 compatibility
// Behave okay if the setCharacterEncoding() method is unavailable
}
}
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide a new instance of {@link
* IComponentMessagesSource}.
*
* @return an instance of {@link DefaultComponentMessagesSource}
* @since 2.0.4
*/
public IComponentMessagesSource createComponentStringsSource(RequestContext context)
{
return new DefaultComponentMessagesSource();
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide an instance of {@link IScriptSource} that will
* be stored into the {@link ServletContext}. Subclasses may override this method to provide a different
* implementation.
*
* @return an instance of {@link DefaultScriptSource}
* @since 1.0.9
*/
protected IScriptSource createScriptSource(RequestContext context)
{
return new DefaultScriptSource(getResourceResolver());
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide an instance of {@link IPageSource} that will be
* stored into the {@link ServletContext}. Subclasses may override this method to provide a different
* implementation.
*
* @return an instance of {@link PageSource}
* @since 1.0.9
*/
protected IPageSource createPageSource(RequestContext context)
{
return new PageSource(this);
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide an instance of {@link ISpecificationSource} that
* will be stored into the {@link ServletContext}. Subclasses may override this method to provide a different
* implementation.
*
* @return an instance of {@link DefaultSpecificationSource}
* @since 1.0.9
*/
protected ISpecificationSource createSpecificationSource(RequestContext context)
{
return new DefaultSpecificationSource(getResourceResolver(), _specification, _pool);
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide an instance of {@link ITemplateSource} that will
* be stored into the {@link ServletContext}. Subclasses may override this method to provide a different
* implementation.
*
* @return an instance of {@link DefaultTemplateSource}
* @since 1.0.9
*/
protected ITemplateSource createTemplateSource(RequestContext context)
{
return new DefaultTemplateSource();
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to provide an instance of {@link ResourceChecksumSource}
* that will be stored into the {@link ServletContext}. Subclasses may override this method to provide a different
* implementation.
*
* @return an instance of {@link ResourceChecksumSourceImpl} that uses MD5 and Hex encoding.
* @since 3.0.3
*/
protected ResourceChecksumSource createResourceChecksumSource()
{
return new ResourceChecksumSourceImpl("MD5", new Hex());
}
protected ExpressionEvaluator createExpressionEvaluator()
{
_expressionCache = (ExpressionCacheImpl) createExpressionCache();
return new ExpressionEvaluatorImpl(_resolver, _classFactory, _expressionCache, _specification);
}
protected ExpressionCache createExpressionCache()
{
return new ExpressionCacheImpl();
}
/**
* Returns an object which can find resources and classes.
*/
public IResourceResolver getResourceResolver()
{
return _resolver;
}
public ExpressionEvaluator getExpressionEvaluator()
{
return _expressionEvaluator;
}
/**
* Generates a description of the instance. Invokes {@link #extendDescription(ToStringBuilder)} to fill in details
* about the instance.
*
* @see #extendDescription(ToStringBuilder)
*/
public String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append(
"name",
_specification == null
? Tapestry.getMessage("AbstractEngine.unknown-specification")
: _specification.getName());
builder.append("dirty", _dirty);
builder.append("locale", _locale);
builder.append("stateful", _stateful);
builder.append("visit", _visit);
extendDescription(builder);
return builder.toString();
}
/**
* Returns true if the reset service is curently enabled.
*/
public boolean isResetServiceEnabled()
{
return _resetServiceEnabled;
}
/**
* Implemented by subclasses to return the names of the active pages (pages for which recorders exist). May return
* the empty list, but should not return null.
*/
abstract public Collection getActivePageNames();
/**
* Gets the visit object, if it has been created already.
* <p/>
* <p/>
* If the visit is non-null then the {@link #isDirty()} flag is set (because the engine can't tell what the caller
* will <i>do</i> with the visit).
*/
public Object getVisit()
{
if (_visit != null)
markDirty();
return _visit;
}
/**
* Gets the visit object, invoking {@link #createVisit(IRequestCycle)} to create it lazily if needed. If cycle is
* null, the visit will not be lazily created.
* <p/>
* <p/>
* After creating the visit, but before returning, the {@link HttpSession} will be created, and {@link
* #setStateful()} will be invoked.
* <p/>
* <p/>
* Sets the {@link #isDirty()} flag, if the return value is not null.
*/
public Object getVisit(IRequestCycle cycle)
{
if (_visit == null && cycle != null)
{
_visit = createVisit(cycle);
// Now that a visit object exists, we need to force the creation
// of a HttpSession.
cycle.getRequestContext().createSession();
setStateful();
}
if (_visit != null)
markDirty();
return _visit;
}
/**
* Updates the visit object and sets the {@link #isDirty() dirty flag}.
*/
public void setVisit(Object value)
{
_visit = value;
markDirty();
}
public boolean getHasVisit()
{
return _visit != null;
}
/**
* Invoked to lazily create a new visit object when it is first referenced (by {@link #getVisit(IRequestCycle)}).
* This implementation works by looking up the name of the class to instantiate in the {@link #getPropertySource()
* configuration}.
* <p/>
* <p>Subclasses may want to overide this method if some other means of instantiating a visit object is required.
*/
protected Object createVisit(IRequestCycle cycle)
{
String visitClassName;
Class visitClass;
Object result = null;
visitClassName = _propertySource.getPropertyValue(VISIT_CLASS_PROPERTY_NAME);
if (LOG.isDebugEnabled())
LOG.debug("Creating visit object as instance of " + visitClassName);
visitClass = _resolver.findClass(visitClassName);
try
{
result = visitClass.newInstance();
}
catch (Throwable t)
{
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-instantiate-visit", visitClassName),
t);
}
return result;
}
/**
* Returns the global object for the application. The global object is created at the start of the request ({@link
* #setupForRequest(RequestContext)} invokes {@link #createGlobal(RequestContext)} if needed), and is stored into
* the {@link ServletContext}. All instances of the engine for the application share the global object; however,
* the global object is explicitly <em>not</em> replicated to other servers within a cluster.
*
* @since 2.3
*/
public Object getGlobal()
{
return _global;
}
public IScriptSource getScriptSource()
{
return _scriptSource;
}
public boolean isStateful()
{
return _stateful;
}
/**
* Invoked by subclasses to indicate that some state must now be stored in the engine (and that the engine should
* now be stored in the HttpSession). The caller is responsible for actually creating the HttpSession (it will have
* access to the {@link RequestContext}).
*
* @since 1.0.2
*/
protected void setStateful()
{
_stateful = true;
}
/**
* Allows subclasses to include listener methods easily.
*
* @since 1.0.2
*/
public ListenerMap getListeners()
{
if (_listeners == null)
_listeners = new ListenerMap(this);
return _listeners;
}
private static class RedirectAnalyzer
{
private boolean _internal;
private String _location;
private RedirectAnalyzer(String location)
{
if (Tapestry.isBlank(location))
{
_location = "";
_internal = true;
return;
}
_location = location;
_internal = !(location.startsWith("/") || location.indexOf("://") > 0);
}
public void process(IRequestCycle cycle)
{
RequestContext context = cycle.getRequestContext();
if (_internal)
forward(context);
else
redirect(context);
}
private void forward(RequestContext context)
{
HttpServletRequest request = context.getRequest();
HttpServletResponse response = context.getResponse();
RequestDispatcher dispatcher = request.getRequestDispatcher("/" + _location);
if (dispatcher == null)
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-find-dispatcher", _location));
try
{
dispatcher.forward(request, response);
}
catch (ServletException ex)
{
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-forward", _location),
ex);
}
catch (IOException ex)
{
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-forward", _location),
ex);
}
}
private void redirect(RequestContext context)
{
HttpServletResponse response = context.getResponse();
String finalURL = response.encodeRedirectURL(_location);
try
{
response.sendRedirect(finalURL);
}
catch (IOException ex)
{
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-redirect", _location),
ex);
}
}
}
/**
* 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 RequestDispatcher} can be found for local
* resource.
* @since 2.2
*/
protected void handleRedirectException(IRequestCycle cycle, RedirectException ex)
{
String location = ex.getRedirectLocation();
if (LOG.isDebugEnabled())
LOG.debug("Redirecting to: " + location);
RedirectAnalyzer analyzer = new RedirectAnalyzer(location);
analyzer.process(cycle);
}
/**
* Creates a Map of all the services available to the application.
* <p/>
* <p>Note: the Map returned is not synchronized, on the theory that returned map is not further modified and
* therefore threadsafe.
*/
private Map createServiceMap()
{
if (LOG.isDebugEnabled())
LOG.debug("Creating service map.");
ISpecificationSource source = getSpecificationSource();
// Build the initial version of the result map,
// where each value is the *name* of a class.
Map result = new HashMap();
// Do the framework first.
addServices(source.getFrameworkNamespace(), result);
// And allow the application to override the framework.
addServices(source.getApplicationNamespace(), result);
IResourceResolver resolver = getResourceResolver();
Iterator i = result.entrySet().iterator();
while (i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
String name = (String) entry.getKey();
String className = (String) entry.getValue();
if (LOG.isDebugEnabled())
LOG.debug("Creating service " + name + " as instance of " + className);
Class serviceClass = resolver.findClass(className);
try
{
IEngineService service = (IEngineService) serviceClass.newInstance();
String serviceName = service.getName();
if (!service.getName().equals(name))
throw new ApplicationRuntimeException(
Tapestry.format(
"AbstractEngine.service-name-mismatch",
name,
className,
serviceName));
// Replace the class name with an instance
// of the named class.
entry.setValue(service);
}
catch (InstantiationException ex)
{
String message =
Tapestry.format(
"AbstractEngine.unable-to-instantiate-service",
name,
className);
LOG.error(message, ex);
throw new ApplicationRuntimeException(message, ex);
}
catch (IllegalAccessException ex)
{
String message =
Tapestry.format(
"AbstractEngine.unable-to-instantiate-service",
name,
className);
LOG.error(message, ex);
throw new ApplicationRuntimeException(message, ex);
}
}
// Result should not be modified after this point, for threadsafety issues.
// We could wrap it in an unmodifiable, but for efficiency we don't.
return result;
}
/**
* Locates all services in the namespace and adds key/value pairs to the map (name and class name). Then
* recursively descendends into child namespaces to collect more service names.
*
* @since 2.2
*/
private void addServices(INamespace namespace, Map map)
{
List names = namespace.getServiceNames();
int count = names.size();
for (int i = 0; i < count; i++)
{
String name = (String) names.get(i);
map.put(name, namespace.getServiceClassName(name));
}
List namespaceIds = namespace.getChildIds();
count = namespaceIds.size();
for (int i = 0; i < count; i++)
{
String id = (String) namespaceIds.get(i);
addServices(namespace.getChildNamespace(id), map);
}
}
/**
* @since 2.0.4
*/
public IComponentMessagesSource getComponentMessagesSource()
{
return _stringsSource;
}
/**
* @since 2.2
*/
public DataSqueezer getDataSqueezer()
{
return _dataSqueezer;
}
/**
* Invoked from {@link #setupForRequest(RequestContext)} to create a {@link DataSqueezer} when needed (typically,
* just the very first time). This implementation returns a standard, new instance.
*
* @since 2.2
*/
public DataSqueezer createDataSqueezer()
{
return new DataSqueezer(_resolver);
}
/**
* Invoked from {@link #service(RequestContext)} to extract, from the URL, the name of the service. The current
* implementation expects the first pathInfo element to be the service name. At some point in the future, the
* method of constructing and parsing URLs may be abstracted into a developer-selected class.
* <p/>
* <p>Subclasses may override this method if the application defines specific services with unusual URL encoding
* rules.
* <p/>
* <p>This implementation simply extracts the value for query parameter {@link Tapestry#SERVICE_QUERY_PARAMETER_NAME}
* and extracts the service name from that.
* <p/>
* <p/>
* For supporting the JSP tags, this method first checks for attribute {@link Tapestry#TAG_SUPPORT_SERVICE_ATTRIBUTE}.
* If non-null, then {@link Tapestry#TAGSUPPORT_SERVICE} is returned.
*
* @since 2.2
*/
protected String extractServiceName(RequestContext context)
{
if (context.getRequest().getAttribute(Tapestry.TAG_SUPPORT_SERVICE_ATTRIBUTE) != null)
return Tapestry.TAGSUPPORT_SERVICE;
String serviceData = context.getParameter(Tapestry.SERVICE_QUERY_PARAMETER_NAME);
if (serviceData == null)
return Tapestry.HOME_SERVICE;
// The service name is anything before the first slash,
// if there is one.
int slashx = serviceData.indexOf('/');
if (slashx < 0)
return serviceData;
return serviceData.substring(0, slashx);
}
/**
* @since 2.3 *
*/
public IPropertySource getPropertySource()
{
return _propertySource;
}
/**
* @since 3.0.3
*/
public ResourceChecksumSource getResourceChecksumSource()
{
return _resourceChecksumSource;
}
/**
* @since 3.0 *
*/
protected String getExceptionPageName()
{
return EXCEPTION_PAGE;
}
/**
* @since 3.0 *
*/
protected String getStaleLinkPageName()
{
return STALE_LINK_PAGE;
}
/**
* @since 3.0 *
*/
protected String getStaleSessionPageName()
{
return STALE_SESSION_PAGE;
}
/**
* Name of an application extension that can provide configuration properties.
*
* @see #createPropertySource(RequestContext)
* @since 2.3
*/
private static final String EXTENSION_PROPERTY_SOURCE_NAME =
"org.apache.tapestry.property-source";
/**
* Creates a shared property source that will be stored into the servlet context. Subclasses may override this
* method to build thier own search path.
* <p/>
* <p>If the application specification contains an extension named "org.apache.tapestry.property-source" it is
* inserted in the search path just before the property source for JVM System Properties. This is a simple hook at
* allow application-specific methods of obtaining configuration values (typically, from a database or from JMX, in
* some way). Alternately, subclasses may override this method to provide whatever search path is appropriate.
*
* @since 2.3
*/
protected IPropertySource createPropertySource(RequestContext context)
{
DelegatingPropertySource result = new DelegatingPropertySource();
ApplicationServlet servlet = context.getServlet();
IApplicationSpecification spec = servlet.getApplicationSpecification();
result.addSource(new PropertyHolderPropertySource(spec));
result.addSource(new ServletPropertySource(servlet.getServletConfig()));
result.addSource(new ServletContextPropertySource(servlet.getServletContext()));
if (spec.checkExtension(EXTENSION_PROPERTY_SOURCE_NAME))
{
IPropertySource source =
(IPropertySource) spec.getExtension(
EXTENSION_PROPERTY_SOURCE_NAME,
IPropertySource.class);
result.addSource(source);
}
result.addSource(SystemPropertiesPropertySource.getInstance());
// Lastly, add a final source to handle "factory defaults".
ResourceBundle bundle =
ResourceBundle.getBundle("org.apache.tapestry.ConfigurationDefaults");
result.addSource(new ResourceBundlePropertySource(bundle));
return result;
}
/**
* Creates the shared Global object. This implementation looks for an configuration property,
* <code>org.apache.tapestry.global-class</code>, and instantiates that class using a no-arguments constructor. If
* the property is not defined, a synchronized {@link java.util.HashMap} is created.
*
* @since 2.3
*/
protected Object createGlobal(RequestContext context)
{
String className = _propertySource.getPropertyValue("org.apache.tapestry.global-class");
if (className == null)
return Collections.synchronizedMap(new HashMap());
Class globalClass = _resolver.findClass(className);
try
{
return globalClass.newInstance();
}
catch (Exception ex)
{
throw new ApplicationRuntimeException(
Tapestry.format("AbstractEngine.unable-to-instantiate-global", className),
ex);
}
}
/**
* Returns an new instance of {@link Pool}, with the standard set of adaptors, plus {@link
* BSFManagerPoolableAdaptor} for {@link BSFManager}.
* <p/>
* <p>Subclasses may override this method to configure the Pool differently.
*
* @since 3.0
*/
protected Pool createPool(RequestContext context)
{
Pool result = new Pool();
result.registerAdaptor(BSFManager.class, new BSFManagerPoolableAdaptor());
return result;
}
/**
* @since 3.0 *
*/
public Pool getPool()
{
return _pool;
}
/**
* Invoked from {@link #setupForRequest(RequestContext)}. Creates a new instance of {@link
* DefaultComponentClassEnhancer}. Subclasses may override to return a different object.
* <p/>
* <p/>
* Check the property <code>org.apache.tapestry.enhance.disable-abstract-method-validation</code> and, if true,
* disables abstract method validation. This is used in some errant JDK's (such as IBM's 1.3.1) that incorrectly
* report concrete methods from abstract classes as abstract.
*
* @since 3.0
*/
protected IComponentClassEnhancer createComponentClassEnhancer(RequestContext context)
{
boolean disableValidation =
"true".equals(
_propertySource.getPropertyValue(
"org.apache.tapestry.enhance.disable-abstract-method-validation"));
return new DefaultComponentClassEnhancer(_resolver, disableValidation, _classFactory);
}
protected IEnhancedClassFactory createClassFactory()
{
return new EnhancedClassFactory(_resolver);
}
/**
* @since 3.0 *
*/
public IComponentClassEnhancer getComponentClassEnhancer()
{
return _enhancer;
}
/**
* Returns true if the engine has (potentially) changed state since the last time it was stored into the {@link
* javax.servlet.http.HttpSession}. Various events set this property to true.
*
* @since 3.0
*/
public boolean isDirty()
{
return _dirty;
}
/**
* Invoked to set the dirty flag, indicating that the engine should be stored into the {@link
* javax.servlet.http.HttpSession}.
*
* @since 3.0
*/
protected void markDirty()
{
if (!_dirty)
LOG.debug("Setting dirty flag.");
_dirty = true;
}
/**
* Clears the dirty flag when a engine is stored into the {@link HttpSession}.
*
* @since 3.0
*/
public void valueBound(HttpSessionBindingEvent arg0)
{
LOG.debug(_dirty ? "Clearing dirty flag." : "Dirty flag already cleared.");
_dirty = false;
}
/**
* Does nothing.
*
* @since 3.0
*/
public void valueUnbound(HttpSessionBindingEvent arg0)
{
}
/**
* The encoding to be used if none has been defined using the output encoding property. Override this method to
* change the default.
*
* @return the default output encoding
* @since 3.0
*/
protected String getDefaultOutputEncoding()
{
return DEFAULT_OUTPUT_ENCODING;
}
/**
* Returns the encoding to be used to generate the servlet responses and accept the servlet requests.
* <p/>
* The encoding is defined using the org.apache.tapestry.output-encoding and is UTF-8 by default
*
* @see org.apache.tapestry.IEngine#getOutputEncoding()
* @since 3.0
*/
public String getOutputEncoding()
{
if (_outputEncoding != null)
return _outputEncoding;
IPropertySource source = getPropertySource();
String encoding = source.getPropertyValue(OUTPUT_ENCODING_PROPERTY_NAME);
if (encoding == null)
encoding = getDefaultOutputEncoding();
_outputEncoding = encoding;
return _outputEncoding;
}
}