| /* |
| * ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2002 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" and |
| * "Apache Tapestry" must not be used to endorse or promote products |
| * derived from this software without prior written permission. For |
| * written permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * "Apache Tapestry", nor may "Apache" appear in their name, without |
| * prior written permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package net.sf.tapestry.engine; |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpSession; |
| import javax.servlet.http.HttpSessionBindingEvent; |
| import javax.servlet.http.HttpSessionBindingListener; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import net.sf.tapestry.ApplicationRuntimeException; |
| import net.sf.tapestry.ApplicationServlet; |
| import net.sf.tapestry.IComponentStringsSource; |
| import net.sf.tapestry.IEngine; |
| import net.sf.tapestry.IEngineService; |
| import net.sf.tapestry.IEngineServiceView; |
| import net.sf.tapestry.IMarkupWriter; |
| import net.sf.tapestry.IMonitor; |
| import net.sf.tapestry.INamespace; |
| import net.sf.tapestry.IPage; |
| import net.sf.tapestry.IPageRecorder; |
| import net.sf.tapestry.IPageSource; |
| import net.sf.tapestry.IPropertySource; |
| import net.sf.tapestry.IRequestCycle; |
| import net.sf.tapestry.IResourceResolver; |
| import net.sf.tapestry.IScriptSource; |
| import net.sf.tapestry.ISpecificationSource; |
| import net.sf.tapestry.ITemplateSource; |
| import net.sf.tapestry.PageRedirectException; |
| import net.sf.tapestry.RedirectException; |
| import net.sf.tapestry.RequestContext; |
| import net.sf.tapestry.RequestCycleException; |
| import net.sf.tapestry.ResponseOutputStream; |
| import net.sf.tapestry.StaleLinkException; |
| import net.sf.tapestry.StaleSessionException; |
| import net.sf.tapestry.Tapestry; |
| import net.sf.tapestry.listener.ListenerMap; |
| import net.sf.tapestry.pageload.PageSource; |
| import net.sf.tapestry.spec.IApplicationSpecification; |
| import net.sf.tapestry.util.DelegatingPropertySource; |
| import net.sf.tapestry.util.PropertyHolderPropertySource; |
| import net.sf.tapestry.util.ServletContextPropertySource; |
| import net.sf.tapestry.util.ServletPropertySource; |
| import net.sf.tapestry.util.SystemPropertiesPropertySource; |
| import net.sf.tapestry.util.exception.ExceptionAnalyzer; |
| import net.sf.tapestry.util.io.DataSqueezer; |
| import net.sf.tapestry.util.prop.OgnlUtils; |
| |
| /** |
| * Basis for building real Tapestry applications. Immediate subclasses |
| * provide different strategies for managing page state and other resources |
| * between request cycles. |
| * |
| * Uses a shared instance of |
| * {@link ITemplateSource}, {@link ISpecificationSource}, |
| * {@link IScriptSource} and {@link net.sf.tapestry.IComponentStringsSource} |
| * stored as attributes of the {@link ServletContext} |
| * (they will be shared by all sessions). |
| * |
| * <p>An application is designed to be very lightweight. |
| * Particularily, it should <b>never</b> hold references to any |
| * {@link IPage} or {@link net.sf.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. They |
| * can be restored inside {@link #setupForRequest(RequestContext)}. |
| * |
| * <p>In practice, a subclass (usually {@link SimpleEngine}) |
| * is used without subclassing. Instead, a |
| * visit object is specified. To facilitate this, the application specification |
| * may include a property, <code>net.sf.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>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>net.sf.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>net.sf.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; |
| |
| /** |
| * Set to true just before the engine is "refreshed" into the |
| * HttpSesson, which causes it to ignore {@link #valueUnbound(HttpSessionBindingEvent)} |
| * events. |
| * |
| * @since 2.2 |
| * |
| **/ |
| |
| private transient boolean _refreshing; |
| |
| /** @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 = "net.sf.tapestry.global"; |
| |
| /** |
| * 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 = "net.sf.tapestry.ScriptSource"; |
| |
| /** |
| * The name of the context attribute for the {@link net.sf.tapestry.IComponentStringsSource} |
| * instance. The application's name is appended. |
| * |
| * @since 2.0.4 |
| * |
| **/ |
| |
| protected static final String STRINGS_SOURCE_NAME = "net.sf.tapestry.StringsSource"; |
| |
| private transient IComponentStringsSource _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 = "net.sf.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 = "net.sf.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 = "net.sf.tapestry.SpecificationSource"; |
| |
| /** |
| * Servlet context attribute name for the {@link IPageSource} |
| * instance. The application's name is appended. |
| * |
| **/ |
| |
| protected static final String PAGE_SOURCE_NAME = "net.sf.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 = "net.sf.tapestry.DataSqueezer"; |
| |
| /** |
| * 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>net.sf.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("net.sf.tapestry.enable-reset-service"); |
| |
| /** |
| * If true (set from the JVM system parameter |
| * <code>net.sf.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("net.sf.tapestry.disable-caching"); |
| |
| private transient IResourceResolver _resolver; |
| |
| /** |
| * Constant used to store a {@link net.sf.tapestry.util.IPropertyHolder} |
| * in the servlet context. |
| * |
| * @since 2.3 |
| * |
| **/ |
| |
| protected static final String PROPERTY_SOURCE_NAME = "net.sf.tapestry.PropertySource"; |
| |
| 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 = "net.sf.tapestry.ServiceMap"; |
| |
| /** |
| * Sets the Exception page's exception property, then renders the Exception page. |
| * |
| * <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(EXCEPTION_PAGE); |
| |
| OgnlUtils.set("exception", _resolver, exceptionPage, cause); |
| |
| cycle.setPage(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.getString("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.getString("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. Subclasses |
| * should invoke this implementation first. They should append a space |
| * before each value. |
| * |
| * @see #toString() |
| **/ |
| |
| public void extendDescription(StringBuffer buffer) |
| { |
| // In rare cases, toString() may be invoked before |
| // the engine has a change to obtain the specification |
| // from the servlet. |
| |
| if (_specification == null) |
| buffer.append(Tapestry.getString("AbstractEngine.unknown-specification")); |
| else |
| buffer.append(_specification.getName()); |
| } |
| |
| /** |
| * 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}. May return null. |
| * |
| * <p>The monitor is used to create a {@link RequestCycle}. |
| * |
| * <p>This implementation returns null always. Subclasses may overide without |
| * invoking it. |
| * |
| * <p>TBD: Lifecycle of the monitor ... should there be a commit? |
| * |
| **/ |
| |
| public IMonitor getMonitor(RequestContext context) |
| { |
| return null; |
| } |
| |
| 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.getString("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 net.sf.tapestry.asset.ContextAsset |
| * |
| **/ |
| |
| public String getContextPath() |
| { |
| return _contextPath; |
| } |
| |
| /** |
| * Returns the specification, if available, or null otherwise. |
| * |
| * <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>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: |
| * |
| * <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, |
| RequestCycleException exception) |
| throws IOException, RequestCycleException, ServletException |
| { |
| // Discard any output from the previous page. |
| |
| out.reset(); |
| |
| IPage page = cycle.getPage(pageName); |
| |
| cycle.setPage(page); |
| |
| renderResponse(cycle, out); |
| } |
| |
| public void renderResponse(IRequestCycle cycle, ResponseOutputStream output) |
| throws RequestCycleException, ServletException, IOException |
| { |
| IMarkupWriter writer; |
| boolean discard = true; |
| IPage page; |
| |
| 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. |
| |
| page = cycle.getPage(); |
| |
| writer = page.getResponseWriter(output); |
| |
| output.setContentType(writer.getContentType()); |
| |
| 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>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(); |
| RequestCycle cycle = null; |
| ResponseOutputStream output = null; |
| IMonitor monitor; |
| |
| if (LOG.isInfoEnabled()) |
| LOG.info("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); |
| |
| cycle = new RequestCycle(this, context, monitor); |
| |
| output = new ResponseOutputStream(context.getResponse()); |
| } |
| catch (Exception ex) |
| { |
| reportException(Tapestry.getString("AbstractEngine.unable-to-begin-request"), ex); |
| |
| throw new ServletException(ex.getMessage(), ex); |
| } |
| |
| IEngineService service = null; |
| |
| try |
| { |
| try |
| { |
| String serviceName = extractServiceName(context); |
| |
| if (Tapestry.isNull(serviceName)) |
| serviceName = IEngineService.HOME_SERVICE; |
| |
| service = getService(serviceName); |
| |
| cycle.setService(service); |
| |
| if (monitor != null) |
| monitor.serviceBegin(service.getName(), context.getRequestURI()); |
| |
| return service.service(this, cycle, output); |
| } |
| catch (PageRedirectException ex) |
| { |
| redirect(ex.getTargetPageName(), cycle, output, ex); |
| } |
| catch (RedirectException ex) |
| { |
| redirectOut(cycle, ex); |
| } |
| catch (StaleLinkException ex) |
| { |
| handleStaleLinkException(ex, cycle, output); |
| } |
| catch (StaleSessionException ex) |
| { |
| handleStaleSessionException(ex, cycle, output); |
| } |
| finally |
| { |
| if (monitor != null) |
| monitor.serviceEnd(service.getName()); |
| } |
| } |
| catch (Exception ex) |
| { |
| if (monitor != null) |
| 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.isInfoEnabled()) |
| LOG.info("Uncaught exception", ex); |
| |
| activateExceptionPage(cycle, output, ex); |
| } |
| finally |
| { |
| cycle.cleanup(); |
| |
| // Closing the buffered output closes the underlying stream as well. |
| |
| if (output != null) |
| output.forceFlush(); |
| |
| cleanupAfterRequest(cycle); |
| |
| if (_disableCaching) |
| { |
| try |
| { |
| clearCachedData(); |
| } |
| catch (Exception ex) |
| { |
| LOG.warn("Exception thrown while clearing caches.", ex); |
| } |
| } |
| |
| if (LOG.isInfoEnabled()) |
| LOG.info("End service"); |
| |
| } |
| |
| // When in doubt, assume that the request did cause some change |
| // to the engine. |
| |
| return true; |
| } |
| |
| /** |
| * Invoked by {@link #service(RequestContext)} if a {@link StaleLinkException} |
| * is thrown by the {@link IEngineService service}. This implementation |
| * invokes |
| * {@link #redirect(String, IRequestCycle, ResponseOutputStream, RequestCycleException)} |
| * to render the StaleLink page. |
| * |
| * <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 handleStaleLinkException(StaleLinkException ex, IRequestCycle cycle, ResponseOutputStream output) |
| throws IOException, ServletException, RequestCycleException |
| { |
| redirect(STALE_LINK_PAGE, 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, RequestCycleException)} |
| * to render the StaleSession page. |
| * |
| * <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, RequestCycleException |
| { |
| redirect(STALE_SESSION_PAGE, cycle, output, ex); |
| } |
| |
| /** |
| * Discards all cached pages, component specifications and templates. |
| * Subclasses who override this implementation should invoke it as |
| * well. |
| * |
| * @since 1.0.1 |
| * |
| **/ |
| |
| public void clearCachedData() |
| { |
| _pageSource.reset(); |
| _specificationSource.reset(); |
| _templateSource.reset(); |
| _scriptSource.reset(); |
| _stringsSource.reset(); |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| * 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>The servlet path is retrieved from {@link HttpServletRequest#getServletPath()}. |
| * |
| * <p>The context path is retrieved from {@link HttpServletRequest#getContextPath()}. |
| * |
| * <p>The final path is available via the {@link #getServletPath()} method. |
| * |
| * <p>In addition, this method locates and/or creates the: |
| * <ul> |
| * <li>{@link ITemplateSource} |
| * <li>{@link ISpecificationSource} |
| * <li>{@link IPageSource} |
| * <li>{@link IEngineService} {@link Map} |
| * <ll>{@link IScriptSource} |
| * <li>{@link IComponentStringsSource} |
| * <li>{@link IPropertySource} |
| * </ul> |
| * |
| * <p>Subclasses should invoke this implementation first, then perform their |
| * own setup. |
| * |
| **/ |
| |
| 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; |
| |
| _clientAddress = request.getRemoteHost(); |
| 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 |
| |
| String 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(); |
| |
| if (_templateSource == null) |
| { |
| String name = TEMPLATE_SOURCE_NAME + "." + servletName; |
| |
| _templateSource = (ITemplateSource) servletContext.getAttribute(name); |
| |
| if (_templateSource == null) |
| { |
| _templateSource = createTemplateSource(); |
| |
| servletContext.setAttribute(name, _templateSource); |
| } |
| } |
| |
| if (_specificationSource == null) |
| { |
| String name = SPECIFICATION_SOURCE_NAME + "." + servletName; |
| |
| _specificationSource = (ISpecificationSource) servletContext.getAttribute(name); |
| |
| if (_specificationSource == null) |
| { |
| _specificationSource = createSpecificationSource(); |
| |
| servletContext.setAttribute(name, _specificationSource); |
| } |
| } |
| |
| if (_pageSource == null) |
| { |
| String name = PAGE_SOURCE_NAME + "." + servletName; |
| |
| _pageSource = (IPageSource) servletContext.getAttribute(name); |
| |
| if (_pageSource == null) |
| { |
| _pageSource = createPageSource(); |
| |
| servletContext.setAttribute(name, _pageSource); |
| } |
| } |
| |
| if (_scriptSource == null) |
| { |
| String name = SCRIPT_SOURCE_NAME + "." + servletName; |
| |
| _scriptSource = (IScriptSource) servletContext.getAttribute(name); |
| |
| if (_scriptSource == null) |
| { |
| _scriptSource = createScriptSource(); |
| |
| 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 = (IComponentStringsSource) servletContext.getAttribute(name); |
| |
| if (_stringsSource == null) |
| { |
| _stringsSource = createComponentStringsSource(); |
| |
| 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 (_propertySource == null) |
| { |
| String name = PROPERTY_SOURCE_NAME + "." + servletName; |
| |
| _propertySource = (IPropertySource) servletContext.getAttribute(name); |
| |
| if (_propertySource == null) |
| { |
| _propertySource = createPropertySource(context); |
| |
| servletContext.setAttribute(name, _propertySource); |
| } |
| } |
| |
| if (_global == null) |
| { |
| String name = GLOBAL_NAME + "." + servletName; |
| |
| _global = servletContext.getAttribute(name); |
| |
| if (_global == null) |
| { |
| _global = createGlobal(context); |
| |
| servletContext.setAttribute(name, _global); |
| } |
| } |
| } |
| |
| /** |
| * |
| * Invoked from {@link #setupForRequest(RequestContext)} to provide |
| * a new instance of {@link IComponentStringsSource}. |
| * |
| * @return an instance of {@link DefaultStringsSource} |
| * @since 2.0.4 |
| * |
| **/ |
| |
| public IComponentStringsSource createComponentStringsSource() |
| { |
| return new DefaultStringsSource(getResourceResolver()); |
| } |
| |
| /** |
| * 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() |
| { |
| 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() |
| { |
| return new PageSource(getResourceResolver()); |
| } |
| |
| /** |
| * 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() |
| { |
| return new DefaultSpecificationSource(getResourceResolver(), _specification); |
| } |
| |
| /** |
| * 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() |
| { |
| return new DefaultTemplateSource(getResourceResolver()); |
| } |
| |
| /** |
| * Returns an object which can find resources and classes. |
| * |
| **/ |
| |
| public IResourceResolver getResourceResolver() |
| { |
| return _resolver; |
| } |
| |
| /** |
| * Generates a description of the instance. |
| * Invokes {@link #extendDescription(StringBuffer)} |
| * to fill in details about the instance. |
| * |
| * @see #extendDescription(StringBuffer) |
| * |
| **/ |
| |
| public String toString() |
| { |
| StringBuffer buffer; |
| |
| buffer = new StringBuffer(super.toString()); |
| |
| buffer.append('['); |
| |
| extendDescription(buffer); |
| |
| buffer.append(']'); |
| |
| return buffer.toString(); |
| } |
| |
| /** |
| * Invoked when the application object is stored into |
| * the {@link HttpSession}. This implementation does nothing. |
| * |
| **/ |
| |
| public void valueBound(HttpSessionBindingEvent event) |
| { |
| } |
| |
| /** |
| * Invoked when the application object is removed from the {@link HttpSession}. |
| * This occurs when the session times out or is explicitly invalidated |
| * (for example, by the reset or restart services). Invokes |
| * {@link #cleanupEngine()}. |
| * |
| * <p> |
| * If the refreshing flag is set to true, then the notification is ignored. |
| * (Some servlet containers invoke valueUnbound(), then valueBound() |
| * when setAttribute() is invoked for an existing attribute. |
| * |
| **/ |
| |
| public void valueUnbound(HttpSessionBindingEvent event) |
| { |
| if (_refreshing) |
| return; |
| |
| // Note: there's a possible latent bug here. If cleaning up the |
| // application requires loading any resources (specifically |
| // component specifications) and we need a ResourceResolver and |
| // the application instance is newly deserialized (i.e., was deserialized |
| // so that it could unbound from the HttpSession) ... then |
| // we may trip over a NullPointerException because the resolver |
| // will be null. Don't have a great solution for this! |
| |
| cleanupEngine(); |
| |
| } |
| |
| /** |
| * Invoked when the engine is removed from the {@link HttpSession} i.e., |
| * because the sesssion timed out or was explicitly invalidated. |
| * |
| * <p>Locates all active pages (pages which have been activated) and |
| * invokes {@link IPage#cleanupPage()} on them. This gives |
| * pages a chance to release any long held resources. This primarily |
| * exists so that pages that hold references to stateful session EJBs |
| * can remove those EJBs in a timely manner. |
| * |
| * <p>Subclasses may overide this method to clean up any engine-held |
| * resources, but should invoke this implementation <em>first</em>. |
| **/ |
| |
| protected void cleanupEngine() |
| { |
| if (LOG.isInfoEnabled()) |
| LOG.info(this +" cleanupEngine()"); |
| |
| Collection activePageNames = getActivePageNames(); |
| if (activePageNames.isEmpty()) |
| return; |
| |
| ISpecificationSource specSource = getSpecificationSource(); |
| IPageSource source = getPageSource(); |
| |
| // A bit of a hack, used only when cleaning up the engine and any pages |
| // as the session is invalidated. We don't really have the stuff we |
| // need to create a context. |
| |
| RequestContext fakeContext = null; |
| |
| try |
| { |
| fakeContext = new RequestContext(null, null, null); |
| } |
| catch (IOException ex) |
| { |
| reportException(Tapestry.getString("AbstractEngine.unable-to-create-cleanup-context"), ex); |
| return; |
| } |
| |
| IRequestCycle fakeCycle = new RequestCycle(this, fakeContext, null); |
| |
| Iterator i = activePageNames.iterator(); |
| |
| while (i.hasNext()) |
| { |
| String name = (String) i.next(); |
| |
| try |
| { |
| IPage page = source.getPage(fakeCycle, name, null); |
| IPageRecorder recorder = getPageRecorder(name); |
| |
| recorder.rollback(page); |
| |
| page.cleanupPage(); |
| } |
| catch (Throwable t) |
| { |
| reportException(Tapestry.getString("AbstractEngine.unable-to-cleanup-page", name), t); |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| **/ |
| |
| public Object getVisit() |
| { |
| return _visit; |
| } |
| |
| /** |
| * Gets the visit object, invoking {@link #createVisit(IRequestCycle)} to create |
| * it lazily if needed. |
| * |
| * |
| **/ |
| |
| public Object getVisit(IRequestCycle cycle) |
| { |
| if (_visit == null && cycle != null) |
| { |
| // Force the creation of the HttpSession |
| |
| cycle.getRequestContext().createSession(); |
| |
| _visit = createVisit(cycle); |
| } |
| |
| return _visit; |
| } |
| |
| public void setVisit(Object value) |
| { |
| _visit = value; |
| } |
| |
| 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 |
| * in the application specification. |
| * |
| * <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 = _specification.getProperty(VISIT_CLASS_PROPERTY_NAME); |
| if (visitClassName == null) |
| throw new ApplicationRuntimeException( |
| Tapestry.getString("AbstractEngine.visit-class-property-not-specified", 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.getString("AbstractEngine.unable-to-instantiate-visit", visitClassName), |
| t); |
| } |
| |
| // Now that a visit object exists, we need to force the creation |
| // of a HttpSession. |
| |
| cycle.getRequestContext().createSession(); |
| |
| setStateful(); |
| |
| 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 IRequestCycle _cycle; |
| private boolean _internal; |
| private String _location; |
| |
| private RedirectAnalyzer(String location) |
| { |
| if (Tapestry.isNull(location)) |
| { |
| _location = "/"; |
| _internal = true; |
| |
| return; |
| } |
| |
| _location = location; |
| |
| _internal = !(location.startsWith("/") || location.indexOf("://") > 0); |
| } |
| |
| public void process(IRequestCycle cycle) throws RequestCycleException |
| { |
| RequestContext context = cycle.getRequestContext(); |
| |
| if (_internal) |
| forward(context); |
| else |
| redirect(context); |
| } |
| |
| private void forward(RequestContext context) throws RequestCycleException |
| { |
| HttpServletRequest request = context.getRequest(); |
| HttpServletResponse response = context.getResponse(); |
| |
| RequestDispatcher dispatcher = request.getRequestDispatcher("/" + _location); |
| |
| if (dispatcher == null) |
| throw new RequestCycleException( |
| Tapestry.getString("AbstractEngine.unable-to-find-dispatcher", _location)); |
| |
| try |
| { |
| dispatcher.forward(request, response); |
| } |
| catch (ServletException ex) |
| { |
| throw new RequestCycleException( |
| Tapestry.getString("AbstractEngine.unable-to-forward", _location), |
| null, |
| ex); |
| } |
| catch (IOException ex) |
| { |
| throw new RequestCycleException( |
| Tapestry.getString("AbstractEngine.unable-to-forward", _location), |
| null, |
| ex); |
| } |
| } |
| |
| private void redirect(RequestContext context) throws RequestCycleException |
| { |
| HttpServletResponse response = context.getResponse(); |
| |
| String finalURL = response.encodeRedirectURL(_location); |
| |
| try |
| { |
| response.sendRedirect(finalURL); |
| } |
| catch (IOException ex) |
| { |
| throw new RequestCycleException( |
| Tapestry.getString("AbstractEngine.unable-to-redirect", _location), |
| null, |
| ex); |
| } |
| } |
| |
| } |
| |
| /** |
| * Invoked when a {@link RedirectException} is thrown during the processing of a request. |
| * |
| * @throws RequestCycleException if an {@link IOException} is thrown by the redirect |
| * |
| * @since 1.0.6 |
| * @deprecated To be removed in 2.3. |
| * Override {@link #handleRedirectException(IRequestCycle, RedirectException)} instead. |
| * |
| **/ |
| |
| protected void redirectOut(IRequestCycle cycle, RedirectException ex) throws RequestCycleException |
| { |
| handleRedirectException(cycle, ex); |
| } |
| |
| /** |
| * Invoked when a {@link RedirectException} is thrown during the processing of a request. |
| * |
| * @throws RequestCycleException 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) throws RequestCycleException |
| { |
| String location = ex.getLocation(); |
| |
| 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>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.getString("AbstractEngine.service-name-mismatch", name, serviceClass, serviceName)); |
| |
| // Replace the class name with an instance |
| // of the named class. |
| |
| entry.setValue(service); |
| } |
| catch (InstantiationException ex) |
| { |
| String message = Tapestry.getString("AbstractEngine.unable-to-instantiate-service", name, className); |
| |
| LOG.error(message, ex); |
| |
| throw new ApplicationRuntimeException(message, ex); |
| } |
| catch (IllegalAccessException ex) |
| { |
| String message = Tapestry.getString("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 IComponentStringsSource getComponentStringsSource() |
| { |
| 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>Subclasses may override this method if the application defines |
| * specific services with unusual URL encoding rules. |
| * |
| * <p>This implementation simply extracts the value for |
| * query parameter {@link IEngineService#SERVICE_QUERY_PARAMETER_NAME}. |
| * |
| * @since 2.2 |
| * |
| **/ |
| |
| protected String extractServiceName(RequestContext context) |
| { |
| return context.getParameter(IEngineService.SERVICE_QUERY_PARAMETER_NAME); |
| } |
| |
| /** @since 2.2 **/ |
| |
| public boolean isRefreshing() |
| { |
| return _refreshing; |
| } |
| |
| /** @since 2.2 **/ |
| |
| public void setRefreshing(boolean refreshing) |
| { |
| _refreshing = refreshing; |
| } |
| |
| /** @since 2.3 **/ |
| |
| public IPropertySource getPropertySource() |
| { |
| return _propertySource; |
| } |
| |
| private static final String EXTENSION_PROPERTY_SOURCE_NAME = "net.sf.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>If the application specification contains an extension |
| * named "net.sf.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); |
| |
| result.addSource(source); |
| } |
| |
| result.addSource(SystemPropertiesPropertySource.getInstance()); |
| |
| return result; |
| } |
| |
| /** |
| * Creates the shared Global object. This implementation looks for an configuration |
| * property, <code>net.sf.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("net.sf.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.getString("AbstractEngine.unable-to-instantiate-global", className), |
| ex); |
| } |
| } |
| } |