You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/11/17 20:51:40 UTC

svn commit: r476282 [2/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/corelib/components/ main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/services/ test/java/org/apache/tapestry/interna...

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=476282&r1=476281&r2=476282
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java Fri Nov 17 11:51:39 2006
@@ -12,635 +12,670 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.services;
-
-import java.io.IOException;
-import java.lang.annotation.Annotation;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.annotations.AfterRender;
-import org.apache.tapestry.annotations.AfterRenderBody;
-import org.apache.tapestry.annotations.AfterRenderTemplate;
-import org.apache.tapestry.annotations.BeforeRenderBody;
-import org.apache.tapestry.annotations.BeforeRenderTemplate;
-import org.apache.tapestry.annotations.BeginRender;
-import org.apache.tapestry.annotations.CleanupRender;
-import org.apache.tapestry.annotations.PostBeginRender;
-import org.apache.tapestry.annotations.PreBeginRender;
-import org.apache.tapestry.annotations.SetupRender;
-import org.apache.tapestry.internal.InternalConstants;
-import org.apache.tapestry.internal.bindings.LiteralBindingFactory;
-import org.apache.tapestry.internal.services.ApplicationGlobalsImpl;
-import org.apache.tapestry.internal.services.BindingSourceImpl;
-import org.apache.tapestry.internal.services.ComponentClassFactoryImpl;
-import org.apache.tapestry.internal.services.ComponentClassResolverImpl;
-import org.apache.tapestry.internal.services.ComponentEventDispatcher;
-import org.apache.tapestry.internal.services.ComponentInstantiatorSource;
-import org.apache.tapestry.internal.services.ComponentLifecycleMethodWorker;
-import org.apache.tapestry.internal.services.ComponentResourcesInjectionProvider;
-import org.apache.tapestry.internal.services.ComponentSourceImpl;
-import org.apache.tapestry.internal.services.ComponentWorker;
-import org.apache.tapestry.internal.services.DefaultInjectionProvider;
-import org.apache.tapestry.internal.services.EnvironmentImpl;
-import org.apache.tapestry.internal.services.EnvironmentalWorker;
-import org.apache.tapestry.internal.services.InfrastructureImpl;
-import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
-import org.apache.tapestry.internal.services.InjectWorker;
-import org.apache.tapestry.internal.services.InjectionProvider;
-import org.apache.tapestry.internal.services.InternalModule;
-import org.apache.tapestry.internal.services.LinkFactory;
-import org.apache.tapestry.internal.services.MarkupWriterImpl;
-import org.apache.tapestry.internal.services.MixinWorker;
-import org.apache.tapestry.internal.services.OnEventWorker;
-import org.apache.tapestry.internal.services.PageRenderDispatcher;
-import org.apache.tapestry.internal.services.PageRenderSupportImpl;
-import org.apache.tapestry.internal.services.PageResponseRenderer;
-import org.apache.tapestry.internal.services.ParameterWorker;
-import org.apache.tapestry.internal.services.PersistWorker;
-import org.apache.tapestry.internal.services.PersistentFieldManagerImpl;
-import org.apache.tapestry.internal.services.RequestGlobalsImpl;
-import org.apache.tapestry.internal.services.RequestPageCache;
-import org.apache.tapestry.internal.services.RetainWorker;
-import org.apache.tapestry.internal.services.SessionPersistentFieldStrategy;
-import org.apache.tapestry.internal.services.StaticFilesFilter;
-import org.apache.tapestry.internal.services.SupportsInformalParametersWorker;
-import org.apache.tapestry.internal.services.UnclaimedFieldWorker;
-import org.apache.tapestry.internal.services.WebContextImpl;
-import org.apache.tapestry.internal.services.WebRequestImpl;
-import org.apache.tapestry.internal.services.WebResponseImpl;
-import org.apache.tapestry.ioc.Configuration;
-import org.apache.tapestry.ioc.IOCUtilities;
-import org.apache.tapestry.ioc.MappedConfiguration;
-import org.apache.tapestry.ioc.ObjectProvider;
-import org.apache.tapestry.ioc.OrderedConfiguration;
-import org.apache.tapestry.ioc.ServiceLocator;
-import org.apache.tapestry.ioc.annotations.Contribute;
-import org.apache.tapestry.ioc.annotations.Id;
-import org.apache.tapestry.ioc.annotations.Inject;
-import org.apache.tapestry.ioc.annotations.InjectService;
-import org.apache.tapestry.ioc.annotations.Lifecycle;
-import org.apache.tapestry.ioc.annotations.SubModule;
-import org.apache.tapestry.ioc.services.ChainBuilder;
-import org.apache.tapestry.ioc.services.ClassFactory;
-import org.apache.tapestry.ioc.services.PipelineBuilder;
-import org.apache.tapestry.ioc.services.PropertyShadowBuilder;
-import org.apache.tapestry.ioc.services.TypeCoercer;
-
-/**
- * The root module for Tapestry.
- */
-@Id("tapestry")
-@SubModule(
-{ InternalModule.class })
-public final class TapestryModule
-{
-    private final ChainBuilder _chainBuilder;
-
-    private final PipelineBuilder _pipelineBuilder;
-
-    private final RequestGlobals _requestGlobals;
-
-    private final ApplicationGlobals _applicationGlobals;
-
-    private final PropertyShadowBuilder _shadowBuilder;
-
-    private final RequestPageCache _requestPageCache;
-
-    private final PageResponseRenderer _pageResponseRenderer;
-
-    private final WebRequest _request;
-
-    private final Environment _environment;
-
-    // Yes, you can inject services defined by this module into this module. The service proxy is
-    // created without instantiating the module itself. We're careful about making as many
-    // service builder and contributor methods static as possible to avoid recursive build
-    // exceptions.
-
-    public TapestryModule(@InjectService("tapestry.ioc.PipelineBuilder")
-    PipelineBuilder pipelineBuilder, @InjectService("tapestry.ioc.PropertyShadowBuilder")
-    PropertyShadowBuilder shadowBuilder, @InjectService("RequestGlobals")
-    RequestGlobals requestGlobals, @InjectService("ApplicationGlobals")
-    ApplicationGlobals applicationGlobals, @InjectService("tapestry.ioc.ChainBuilder")
-    ChainBuilder chainBuilder, @InjectService("tapestry.internal.RequestPageCache")
-    RequestPageCache requestPageCache, @InjectService("tapestry.internal.PageResponseRenderer")
-    PageResponseRenderer pageResponseRenderer, @Inject("infrastructure:request")
-    WebRequest request, @InjectService("Environment")
-    Environment environment)
-    {
-        _pipelineBuilder = pipelineBuilder;
-        _shadowBuilder = shadowBuilder;
-        _requestGlobals = requestGlobals;
-        _applicationGlobals = applicationGlobals;
-        _chainBuilder = chainBuilder;
-        _requestPageCache = requestPageCache;
-        _pageResponseRenderer = pageResponseRenderer;
-        _request = request;
-        _environment = environment;
-    }
-
-    private static <T> void add(Configuration<InfrastructureContribution> configuration,
-            ServiceLocator locator, Class<T> serviceInterface)
-    {
-        String className = serviceInterface.getName();
-        int dotx = className.lastIndexOf('.');
-        String serviceId = className.substring(dotx + 1);
-
-        // Convert first character to lower case to form the property name.
-
-        String propertyName = serviceId.substring(0, 1).toLowerCase() + serviceId.substring(1);
-
-        T service = locator.getService(serviceId, serviceInterface);
-
-        InfrastructureContribution contribution = new InfrastructureContribution(propertyName,
-                service);
-
-        configuration.add(contribution);
-    }
-
-    public static ApplicationGlobals buildApplicationGlobals()
-    {
-        return new ApplicationGlobalsImpl();
-    }
-
-    public WebContext buildWebContext(@InjectService("ApplicationGlobals")
-    ApplicationGlobals globals)
-    {
-        return _shadowBuilder.build(globals, "webContext", WebContext.class);
-    }
-
-    public ServletApplicationInitializer buildServletApplicationInitializer(Log log,
-            List<ServletApplicationInitializerFilter> configuration,
-            @InjectService("ApplicationInitializer")
-            final ApplicationInitializer initializer)
-    {
-        ServletApplicationInitializer terminator = new ServletApplicationInitializer()
-        {
-            public void initializeApplication(ServletContext context)
-            {
-                _applicationGlobals.store(context);
-
-                // And now, down the (Web) ApplicationInitializer pipeline ...
-
-                initializer.initializeApplication(new WebContextImpl(context));
-            }
-        };
-
-        return _pipelineBuilder.build(
-                log,
-                ServletApplicationInitializer.class,
-                ServletApplicationInitializerFilter.class,
-                configuration,
-                terminator);
-    }
-
-    /** Initializes the application. */
-    public ApplicationInitializer buildApplicationInitializer(Log log,
-            List<ApplicationInitializerFilter> configuration)
-    {
-        ApplicationInitializer terminator = new ApplicationInitializer()
-        {
-            public void initializeApplication(WebContext context)
-            {
-                _applicationGlobals.store(context);
-            }
-        };
-
-        return _pipelineBuilder.build(
-                log,
-                ApplicationInitializer.class,
-                ApplicationInitializerFilter.class,
-                configuration,
-                terminator);
-    }
-
-    /**
-     * Allows the exact steps in the component class transformation process to be defined.
-     */
-    public ComponentClassTransformWorker buildComponentClassTransformWorker(
-            List<ComponentClassTransformWorker> configuration)
-    {
-        return _chainBuilder.build(ComponentClassTransformWorker.class, configuration);
-    }
-
-    public HttpServletRequestHandler buildHttpServletRequestHandler(Log log,
-            List<HttpServletRequestFilter> configuration, @InjectService("WebRequestHandler")
-            final WebRequestHandler handler)
-    {
-        HttpServletRequestHandler terminator = new HttpServletRequestHandler()
-        {
-            public boolean service(HttpServletRequest request, HttpServletResponse response)
-                    throws IOException
-            {
-                _requestGlobals.store(request, response);
-
-                return handler.service(new WebRequestImpl(request), new WebResponseImpl(response));
-            }
-        };
-
-        return _pipelineBuilder.build(
-                log,
-                HttpServletRequestHandler.class,
-                HttpServletRequestFilter.class,
-                configuration,
-                terminator);
-    }
-
-    public static Infrastructure buildInfrastructure(Log log,
-            Collection<InfrastructureContribution> configuration)
-    {
-        InfrastructureManager manager = new InfrastructureManagerImpl(log, configuration);
-
-        return new InfrastructureImpl(manager);
-    }
-
-    public static MarkupWriterFactory buildMarkupWriterFactory()
-    {
-        // Temporary ...
-        return new MarkupWriterFactory()
-        {
-            public MarkupWriter newMarkupWriter()
-            {
-                return new MarkupWriterImpl();
-            }
-        };
-    }
-
-    /**
-     * Ordered contributions to the MasterDispatcher service allow different URL matching strategies
-     * to occur.
-     */
-    public Dispatcher buildMasterDispatcher(List<Dispatcher> configuration)
-    {
-        return _chainBuilder.build(Dispatcher.class, configuration);
-    }
-
-    @Lifecycle("perthread")
-    public static RequestGlobals buildRequestGlobals()
-    {
-        return new RequestGlobalsImpl();
-    }
-
-    /**
-     * Builds a shadow of the RequestGlobals.request property. Note again that the shadow can be an
-     * ordinary singleton, even though RequestGlobals is perthread.
-     */
-    public WebRequest buildWebRequest()
-    {
-        return _shadowBuilder.build(_requestGlobals, "request", WebRequest.class);
-    }
-
-    /**
-     * Builds a shadow of the RequestGlobals.response property. Note again that the shadow can be an
-     * ordinary singleton, even though RequestGlobals is perthread.
-     */
-    public WebResponse buildWebResponse()
-    {
-        return _shadowBuilder.build(_requestGlobals, "response", WebResponse.class);
-    }
-
-    public WebRequestHandler buildWebRequestHandler(Log log, List<WebRequestFilter> configuration,
-            @InjectService("MasterDispatcher")
-            final Dispatcher masterDispatcher)
-    {
-        WebRequestHandler terminator = new WebRequestHandler()
-        {
-            public boolean service(WebRequest request, WebResponse response) throws IOException
-            {
-                _requestGlobals.store(request, response);
-
-                return masterDispatcher.dispatch(request, response);
-            }
-        };
-
-        return _pipelineBuilder.build(
-                log,
-                WebRequestHandler.class,
-                WebRequestFilter.class,
-                configuration,
-                terminator);
-    }
-
-    /**
-     * Contributes filter "tapestry.StaticFilesFilter" that idenfies requests for static resources
-     * and terminates the pipeline by returning false. Generally, most filters should be ordered
-     * after this filter.
-     */
-    public static void contributeWebRequestHandler(
-            OrderedConfiguration<WebRequestFilter> configuration, @InjectService("WebContext")
-            WebContext webContext,
-            @InjectService("tapestry.internal.DefaultRequestExceptionHandler")
-            final RequestExceptionHandler exceptionHandler)
-    {
-        WebRequestFilter staticFilesFilter = new StaticFilesFilter(webContext);
-
-        configuration.add("StaticFilesFilter", staticFilesFilter);
-
-        WebRequestFilter errorFilter = new WebRequestFilter()
-        {
-            public boolean service(WebRequest request, WebResponse response,
-                    WebRequestHandler handler) throws IOException
-            {
-                try
-                {
-                    return handler.service(request, response);
-                }
-                catch (RuntimeException ex)
-                {
-                    exceptionHandler.handleRequestException(ex);
-
-                    // We assume a reponse has been sent and there's no need to handle the request
-                    // further.
-
-                    return true;
-                }
-            }
-        };
-
-        configuration.add("ErrorFilter", errorFilter);
-    }
-
-    /**
-     * Contributes properties: componentNameExpander, markupWriterFactory, request
-     */
-    public static void contributeInfrastructure(
-            Configuration<InfrastructureContribution> configuration, ServiceLocator locator,
-            @InjectService("WebRequest")
-            WebRequest request, @InjectService("WebResponse")
-            WebResponse response, @InjectService("tapestry.ioc.TypeCoercer")
-            TypeCoercer typeCoercer)
-    {
-        add(configuration, locator, MarkupWriterFactory.class);
-        add(configuration, locator, PersistentFieldManager.class);
-        add(configuration, locator, Environment.class);
-        add(configuration, locator, ComponentSource.class);
-
-        configuration.add(new InfrastructureContribution("request", request));
-        configuration.add(new InfrastructureContribution("response", response));
-        configuration.add(new InfrastructureContribution("typeCoercer", typeCoercer));
-    }
-
-    /**
-     * Contributes the {@link ObjectProvider} provided by {@link Infrastructure#getObjectProvider()}
-     * mapped to the provider prefix "infrastructure".
-     */
-    @Contribute("tapestry.ioc.MasterObjectProvider")
-    public static void contributeInfrastructureToMasterObjectProvider(
-            MappedConfiguration<String, ObjectProvider> configuration,
-            @InjectService("Infrastructure")
-            Infrastructure infrastructure)
-    {
-        configuration.add("infrastructure", infrastructure.getObjectProvider());
-    }
-
-    public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration,
-            @InjectService("tapestry.internal.LinkFactory")
-            LinkFactory linkFactory)
-    {
-        configuration.add(
-                "HTML",
-                new PageRenderDispatcher(_pageResponseRenderer, _requestPageCache));
-
-        // This goes after the HTML one so that the "." in ".html" doesn't confuse it.
-
-        configuration.add("ComponentEvent", new ComponentEventDispatcher(_requestPageCache,
-                linkFactory), "after:HTML");
-    }
-
-    public static ComponentClassResolver buildComponentClassResolver(
-            @InjectService("tapestry.internal.ComponentInstantiatorSource")
-            ComponentInstantiatorSource source, Collection<LibraryMapping> configuration)
-    {
-        ComponentClassResolverImpl service = new ComponentClassResolverImpl(source, configuration);
-
-        // Allow the resolver to clean its cache when the source is invalidated
-
-        source.addInvalidationListener(service);
-
-        return service;
-    }
-
-    public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration)
-    {
-        configuration.add(new LibraryMapping("core", "org.apache.tapestry.corelib"));
-    }
-
-    public static BindingSource buildBindingSource(Map<String, BindingFactory> configuration)
-    {
-        return new BindingSourceImpl(configuration);
-    }
-
-    /** Contributes the factory for "literal:" bindings. */
-    public static void contributeBindingSource(
-            MappedConfiguration<String, BindingFactory> configuration,
-            @InjectService("tapestry.internal.PropBindingFactory")
-            BindingFactory propBindingFactory)
-    {
-        configuration.add(InternalConstants.LITERAL_BINDING_PREFIX, new LiteralBindingFactory());
-        configuration.add(InternalConstants.PROP_BINDING_PREFIX, propBindingFactory);
-    }
-
-    /**
-     * Returns a {@link ClassFactory} that can be used to create extra classes around component
-     * classes.
-     */
-    public static ClassFactory buildComponentClassFactory(Log log,
-            @InjectService("tapestry.internal.ComponentInstantiatorSource")
-            ComponentInstantiatorSource source)
-    {
-        return new ComponentClassFactoryImpl(log, source);
-    }
-
-    /**
-     * A chain of command for providing values for {@link org.apache.tapestry.annotations.Inject}-ed
-     * fields in component classes. The service's configuration can be extended to allow for
-     * different automatic injections (based on some combination of field type and field name).
-     */
-
-    public InjectionProvider buildInjectionProvider(List<InjectionProvider> configuration)
-    {
-        return _chainBuilder.build(InjectionProvider.class, configuration);
-    }
-
-    /**
-     * Contributes the elemental providers:
-     * <ul>
-     * <li>ComponentResources -- give component access to its resources</li>
-     * <li>Default -- looks for a unique IoC service that matches the field type</li>
-     * </ul>
-     */
-    public static void contributeInjectionProvider(
-            OrderedConfiguration<InjectionProvider> configuration)
-    {
-        configuration.add("ComponentResources", new ComponentResourcesInjectionProvider());
-        configuration.add("Default", new DefaultInjectionProvider(), "after:*.*");
-    }
-
-    /**
-     * Adds a number of standard component class transform workers:
-     * <ul>
-     * <li>Retain -- allows fields to retain their values between requests</li>
-     * <li>Persist -- allows fields to store their their value persistently between requests</li>
-     * <li>Parameter -- identifies parameters based on the
-     * {@link org.apache.tapestry.annotations.Parameter} annotation</li>
-     * <li>Component -- identifies embedded components based on the
-     * {@link org.apache.tapestry.annotations.Component} annotation</li>
-     * <li>Mixin -- adds a mixin as part of a component's implementation</li>
-     * <li>Environment -- allows fields to contain values extracted from the {@link Environment}
-     * service</li>
-     * <li>SupportsInformalParameters -- checks for the annotation</li>
-     * <li>UnclaimedField -- identifies unclaimed fields and resets them to null/0/false at the end
-     * of the request</li>
-     * <li>SetupRender, BeginRender, etc. -- correspond to component render phases and annotations</li>
-     * </ul>
-     */
-    public static void contributeComponentClassTransformWorker(
-            OrderedConfiguration<ComponentClassTransformWorker> configuration,
-            ServiceLocator locator, @InjectService("tapestry.ioc.MasterObjectProvider")
-            ObjectProvider objectProvider, @InjectService("InjectionProvider")
-            InjectionProvider injectionProvider, @InjectService("Environment")
-            Environment environment, @InjectService("tapestry.ComponentClassResolver")
-            ComponentClassResolver resolver)
-    {
-        // TODO: Proper scheduling of all of this. Since a given field or method should
-        // only have a single annotation, the order doesn't matter so much, as long as
-        // UnclaimedField is last.
-
-        configuration.add("Inject", new InjectWorker(objectProvider, locator, injectionProvider));
-        configuration.add("Parameter", new ParameterWorker());
-        configuration.add("Component", new ComponentWorker(resolver));
-        configuration.add("Environment", new EnvironmentalWorker(environment));
-        configuration.add("Mixin", new MixinWorker(resolver));
-        configuration.add("OnEvent", new OnEventWorker());
-        configuration.add("SupportsInformalParameters", new SupportsInformalParametersWorker());
-
-        // Workers for the component rendering state machine methods; this is in typical
-        // execution order.
-
-        add(configuration, TransformConstants.SETUP_RENDER_SIGNATURE, SetupRender.class, false);
-        add(
-                configuration,
-                TransformConstants.PRE_BEGIN_RENDER_SIGNATURE,
-                PreBeginRender.class,
-                false);
-        add(configuration, TransformConstants.BEGIN_RENDER_SIGNATURE, BeginRender.class, false);
-        add(
-                configuration,
-                TransformConstants.POST_BEGIN_RENDER_SIGNATURE,
-                PostBeginRender.class,
-                false);
-        add(
-                configuration,
-                TransformConstants.BEFORE_RENDER_TEMPLATE_SIGNATURE,
-                BeforeRenderTemplate.class,
-                false);
-        add(
-                configuration,
-                TransformConstants.BEFORE_RENDER_BODY_SIGNATURE,
-                BeforeRenderBody.class,
-                false);
-
-        // These phases operate in reverse order.
-
-        add(
-                configuration,
-                TransformConstants.AFTER_RENDER_BODY_SIGNATURE,
-                AfterRenderBody.class,
-                true);
-        add(
-                configuration,
-                TransformConstants.AFTER_RENDER_TEMPLATE_SIGNATURE,
-                AfterRenderTemplate.class,
-                true);
-        add(configuration, TransformConstants.AFTER_RENDER_SIGNATURE, AfterRender.class, true);
-        add(configuration, TransformConstants.CLEANUP_RENDER_SIGNATURE, CleanupRender.class, true);
-
-        configuration.add("Retain", new RetainWorker());
-        configuration.add("Persist", new PersistWorker());
-        configuration.add("UnclaimedField", new UnclaimedFieldWorker(), "after:*.*");
-    }
-
-    private static void add(OrderedConfiguration<ComponentClassTransformWorker> configuration,
-            MethodSignature signature, Class<? extends Annotation> annotationClass, boolean reverse)
-    {
-        // make the name match the annotation class name.
-
-        String name = IOCUtilities.toSimpleId(annotationClass.getName());
-
-        configuration.add(name, new ComponentLifecycleMethodWorker(signature, annotationClass,
-                reverse));
-    }
-
-    @Lifecycle("perthread")
-    public static Environment buildEnvironment()
-    {
-        return new EnvironmentImpl();
-    }
-
-    /**
-     * A service that acts primarily as a configuration point for render initializers (each
-     * implements Runnable). All of these initializers are invoked at the start of the page
-     * rendering process. The primary use of this is to initialize environmental services inside the
-     * {@link Environment}. The Environment is cleared before any of the contributions are
-     * executed.
-     */
-    public Runnable buildPageRenderInitializer(final Collection<Runnable> configuration)
-    {
-        return new Runnable()
-        {
-            public void run()
-            {
-                _environment.clear();
-
-                for (Runnable r : configuration)
-                    r.run();
-            }
-        };
-    }
-
-    public void contributePageRenderInitializer(Configuration<Runnable> configuration)
-    {
-        // I'm not happy with this lifecycle, per-se. Instead of Runnable, I think we need an
-        // interface more tailored to Environment, with perhaps a second method for the end of
-        // the page render.
-
-        configuration.add(new Runnable()
-        {
-            public void run()
-            {
-                _environment.push(PageRenderSupport.class, new PageRenderSupportImpl());
-            }
-        });
-    }
-
-    /** A public service since extensions may provide new persistent strategies. */
-    public static PersistentFieldManager buildPersistentFieldManager(
-            Map<String, PersistentFieldStrategy> configuration)
-    {
-        return new PersistentFieldManagerImpl(configuration);
-    }
-
-    /**
-     * Contributes the "session" strategy.
-     */
-    public void contributePersistentFieldManager(
-            MappedConfiguration<String, PersistentFieldStrategy> configuration)
-    {
-        configuration.add("session", new SessionPersistentFieldStrategy(_request));
-    }
-
-    public ComponentSource buildComponentSource(
-            @InjectService("tapestry.internal.RequestPageCache")
-            RequestPageCache pageCache)
-    {
-        return new ComponentSourceImpl(pageCache);
-    }
-}
+package org.apache.tapestry.services;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.annotations.AfterRender;
+import org.apache.tapestry.annotations.AfterRenderBody;
+import org.apache.tapestry.annotations.AfterRenderTemplate;
+import org.apache.tapestry.annotations.BeforeRenderBody;
+import org.apache.tapestry.annotations.BeforeRenderTemplate;
+import org.apache.tapestry.annotations.BeginRender;
+import org.apache.tapestry.annotations.CleanupRender;
+import org.apache.tapestry.annotations.PostBeginRender;
+import org.apache.tapestry.annotations.PreBeginRender;
+import org.apache.tapestry.annotations.SetupRender;
+import org.apache.tapestry.internal.InternalConstants;
+import org.apache.tapestry.internal.bindings.LiteralBindingFactory;
+import org.apache.tapestry.internal.services.ApplicationGlobalsImpl;
+import org.apache.tapestry.internal.services.BindingSourceImpl;
+import org.apache.tapestry.internal.services.ComponentClassFactoryImpl;
+import org.apache.tapestry.internal.services.ComponentClassResolverImpl;
+import org.apache.tapestry.internal.services.ComponentEventDispatcher;
+import org.apache.tapestry.internal.services.ComponentInstantiatorSource;
+import org.apache.tapestry.internal.services.ComponentLifecycleMethodWorker;
+import org.apache.tapestry.internal.services.ComponentResourcesInjectionProvider;
+import org.apache.tapestry.internal.services.ComponentSourceImpl;
+import org.apache.tapestry.internal.services.ComponentWorker;
+import org.apache.tapestry.internal.services.DefaultInjectionProvider;
+import org.apache.tapestry.internal.services.EnvironmentImpl;
+import org.apache.tapestry.internal.services.EnvironmentalWorker;
+import org.apache.tapestry.internal.services.HeartbeatImpl;
+import org.apache.tapestry.internal.services.InfrastructureImpl;
+import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
+import org.apache.tapestry.internal.services.InjectWorker;
+import org.apache.tapestry.internal.services.InjectionProvider;
+import org.apache.tapestry.internal.services.InternalModule;
+import org.apache.tapestry.internal.services.LinkFactory;
+import org.apache.tapestry.internal.services.MarkupWriterImpl;
+import org.apache.tapestry.internal.services.MixinWorker;
+import org.apache.tapestry.internal.services.OnEventWorker;
+import org.apache.tapestry.internal.services.PageRenderDispatcher;
+import org.apache.tapestry.internal.services.PageRenderSupportImpl;
+import org.apache.tapestry.internal.services.PageResponseRenderer;
+import org.apache.tapestry.internal.services.ParameterWorker;
+import org.apache.tapestry.internal.services.PersistWorker;
+import org.apache.tapestry.internal.services.PersistentFieldManagerImpl;
+import org.apache.tapestry.internal.services.RequestGlobalsImpl;
+import org.apache.tapestry.internal.services.RequestPageCache;
+import org.apache.tapestry.internal.services.RetainWorker;
+import org.apache.tapestry.internal.services.SessionPersistentFieldStrategy;
+import org.apache.tapestry.internal.services.StaticFilesFilter;
+import org.apache.tapestry.internal.services.SupportsInformalParametersWorker;
+import org.apache.tapestry.internal.services.UnclaimedFieldWorker;
+import org.apache.tapestry.internal.services.WebContextImpl;
+import org.apache.tapestry.internal.services.WebRequestImpl;
+import org.apache.tapestry.internal.services.WebResponseImpl;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.Configuration;
+import org.apache.tapestry.ioc.IOCUtilities;
+import org.apache.tapestry.ioc.MappedConfiguration;
+import org.apache.tapestry.ioc.ObjectProvider;
+import org.apache.tapestry.ioc.OrderedConfiguration;
+import org.apache.tapestry.ioc.ServiceLocator;
+import org.apache.tapestry.ioc.annotations.Contribute;
+import org.apache.tapestry.ioc.annotations.Id;
+import org.apache.tapestry.ioc.annotations.Inject;
+import org.apache.tapestry.ioc.annotations.InjectService;
+import org.apache.tapestry.ioc.annotations.Lifecycle;
+import org.apache.tapestry.ioc.annotations.SubModule;
+import org.apache.tapestry.ioc.services.ChainBuilder;
+import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.PipelineBuilder;
+import org.apache.tapestry.ioc.services.PropertyShadowBuilder;
+import org.apache.tapestry.ioc.services.TypeCoercer;
+
+/**
+ * The root module for Tapestry.
+ */
+@Id("tapestry")
+@SubModule(
+{ InternalModule.class })
+public final class TapestryModule
+{
+    private final ChainBuilder _chainBuilder;
+
+    private final PipelineBuilder _pipelineBuilder;
+
+    private final RequestGlobals _requestGlobals;
+
+    private final ApplicationGlobals _applicationGlobals;
+
+    private final PropertyShadowBuilder _shadowBuilder;
+
+    private final RequestPageCache _requestPageCache;
+
+    private final PageResponseRenderer _pageResponseRenderer;
+
+    private final WebRequest _request;
+
+    private final Environment _environment;
+
+    // Yes, you can inject services defined by this module into this module. The service proxy is
+    // created without instantiating the module itself. We're careful about making as many
+    // service builder and contributor methods static as possible to avoid recursive build
+    // exceptions.
+
+    public TapestryModule(@InjectService("tapestry.ioc.PipelineBuilder")
+    PipelineBuilder pipelineBuilder, @InjectService("tapestry.ioc.PropertyShadowBuilder")
+    PropertyShadowBuilder shadowBuilder, @InjectService("RequestGlobals")
+    RequestGlobals requestGlobals, @InjectService("ApplicationGlobals")
+    ApplicationGlobals applicationGlobals, @InjectService("tapestry.ioc.ChainBuilder")
+    ChainBuilder chainBuilder, @InjectService("tapestry.internal.RequestPageCache")
+    RequestPageCache requestPageCache, @InjectService("tapestry.internal.PageResponseRenderer")
+    PageResponseRenderer pageResponseRenderer, @Inject("infrastructure:request")
+    WebRequest request, @InjectService("Environment")
+    Environment environment)
+    {
+        _pipelineBuilder = pipelineBuilder;
+        _shadowBuilder = shadowBuilder;
+        _requestGlobals = requestGlobals;
+        _applicationGlobals = applicationGlobals;
+        _chainBuilder = chainBuilder;
+        _requestPageCache = requestPageCache;
+        _pageResponseRenderer = pageResponseRenderer;
+        _request = request;
+        _environment = environment;
+    }
+
+    private static <T> void add(Configuration<InfrastructureContribution> configuration,
+            ServiceLocator locator, Class<T> serviceInterface)
+    {
+        String className = serviceInterface.getName();
+        int dotx = className.lastIndexOf('.');
+        String serviceId = className.substring(dotx + 1);
+
+        // Convert first character to lower case to form the property name.
+
+        String propertyName = serviceId.substring(0, 1).toLowerCase() + serviceId.substring(1);
+
+        T service = locator.getService(serviceId, serviceInterface);
+
+        InfrastructureContribution contribution = new InfrastructureContribution(propertyName,
+                service);
+
+        configuration.add(contribution);
+    }
+
+    public static ApplicationGlobals buildApplicationGlobals()
+    {
+        return new ApplicationGlobalsImpl();
+    }
+
+    public WebContext buildWebContext(@InjectService("ApplicationGlobals")
+    ApplicationGlobals globals)
+    {
+        return _shadowBuilder.build(globals, "webContext", WebContext.class);
+    }
+
+    public ServletApplicationInitializer buildServletApplicationInitializer(Log log,
+            List<ServletApplicationInitializerFilter> configuration,
+            @InjectService("ApplicationInitializer")
+            final ApplicationInitializer initializer)
+    {
+        ServletApplicationInitializer terminator = new ServletApplicationInitializer()
+        {
+            public void initializeApplication(ServletContext context)
+            {
+                _applicationGlobals.store(context);
+
+                // And now, down the (Web) ApplicationInitializer pipeline ...
+
+                initializer.initializeApplication(new WebContextImpl(context));
+            }
+        };
+
+        return _pipelineBuilder.build(
+                log,
+                ServletApplicationInitializer.class,
+                ServletApplicationInitializerFilter.class,
+                configuration,
+                terminator);
+    }
+
+    /** Initializes the application. */
+    public ApplicationInitializer buildApplicationInitializer(Log log,
+            List<ApplicationInitializerFilter> configuration)
+    {
+        ApplicationInitializer terminator = new ApplicationInitializer()
+        {
+            public void initializeApplication(WebContext context)
+            {
+                _applicationGlobals.store(context);
+            }
+        };
+
+        return _pipelineBuilder.build(
+                log,
+                ApplicationInitializer.class,
+                ApplicationInitializerFilter.class,
+                configuration,
+                terminator);
+    }
+
+    /**
+     * Allows the exact steps in the component class transformation process to be defined.
+     */
+    public ComponentClassTransformWorker buildComponentClassTransformWorker(
+            List<ComponentClassTransformWorker> configuration)
+    {
+        return _chainBuilder.build(ComponentClassTransformWorker.class, configuration);
+    }
+
+    public HttpServletRequestHandler buildHttpServletRequestHandler(Log log,
+            List<HttpServletRequestFilter> configuration, @InjectService("WebRequestHandler")
+            final WebRequestHandler handler)
+    {
+        HttpServletRequestHandler terminator = new HttpServletRequestHandler()
+        {
+            public boolean service(HttpServletRequest request, HttpServletResponse response)
+                    throws IOException
+            {
+                _requestGlobals.store(request, response);
+
+                return handler.service(new WebRequestImpl(request), new WebResponseImpl(response));
+            }
+        };
+
+        return _pipelineBuilder.build(
+                log,
+                HttpServletRequestHandler.class,
+                HttpServletRequestFilter.class,
+                configuration,
+                terminator);
+    }
+
+    public static Infrastructure buildInfrastructure(Log log,
+            Collection<InfrastructureContribution> configuration)
+    {
+        InfrastructureManager manager = new InfrastructureManagerImpl(log, configuration);
+
+        return new InfrastructureImpl(manager);
+    }
+
+    public static MarkupWriterFactory buildMarkupWriterFactory()
+    {
+        // Temporary ...
+        return new MarkupWriterFactory()
+        {
+            public MarkupWriter newMarkupWriter()
+            {
+                return new MarkupWriterImpl();
+            }
+        };
+    }
+
+    /**
+     * Ordered contributions to the MasterDispatcher service allow different URL matching strategies
+     * to occur.
+     */
+    public Dispatcher buildMasterDispatcher(List<Dispatcher> configuration)
+    {
+        return _chainBuilder.build(Dispatcher.class, configuration);
+    }
+
+    @Lifecycle("perthread")
+    public static RequestGlobals buildRequestGlobals()
+    {
+        return new RequestGlobalsImpl();
+    }
+
+    /**
+     * Builds a shadow of the RequestGlobals.request property. Note again that the shadow can be an
+     * ordinary singleton, even though RequestGlobals is perthread.
+     */
+    public WebRequest buildWebRequest()
+    {
+        return _shadowBuilder.build(_requestGlobals, "request", WebRequest.class);
+    }
+
+    /**
+     * Builds a shadow of the RequestGlobals.response property. Note again that the shadow can be an
+     * ordinary singleton, even though RequestGlobals is perthread.
+     */
+    public WebResponse buildWebResponse()
+    {
+        return _shadowBuilder.build(_requestGlobals, "response", WebResponse.class);
+    }
+
+    public WebRequestHandler buildWebRequestHandler(Log log, List<WebRequestFilter> configuration,
+            @InjectService("MasterDispatcher")
+            final Dispatcher masterDispatcher)
+    {
+        WebRequestHandler terminator = new WebRequestHandler()
+        {
+            public boolean service(WebRequest request, WebResponse response) throws IOException
+            {
+                _requestGlobals.store(request, response);
+
+                return masterDispatcher.dispatch(request, response);
+            }
+        };
+
+        return _pipelineBuilder.build(
+                log,
+                WebRequestHandler.class,
+                WebRequestFilter.class,
+                configuration,
+                terminator);
+    }
+
+    /**
+     * Contributes filter "tapestry.StaticFilesFilter" that idenfies requests for static resources
+     * and terminates the pipeline by returning false. Generally, most filters should be ordered
+     * after this filter.
+     */
+    public static void contributeWebRequestHandler(
+            OrderedConfiguration<WebRequestFilter> configuration, @InjectService("WebContext")
+            WebContext webContext,
+            @InjectService("tapestry.internal.DefaultRequestExceptionHandler")
+            final RequestExceptionHandler exceptionHandler)
+    {
+        WebRequestFilter staticFilesFilter = new StaticFilesFilter(webContext);
+
+        configuration.add("StaticFilesFilter", staticFilesFilter);
+
+        WebRequestFilter errorFilter = new WebRequestFilter()
+        {
+            public boolean service(WebRequest request, WebResponse response,
+                    WebRequestHandler handler) throws IOException
+            {
+                try
+                {
+                    return handler.service(request, response);
+                }
+                catch (RuntimeException ex)
+                {
+                    exceptionHandler.handleRequestException(ex);
+
+                    // We assume a reponse has been sent and there's no need to handle the request
+                    // further.
+
+                    return true;
+                }
+            }
+        };
+
+        configuration.add("ErrorFilter", errorFilter);
+    }
+
+    /**
+     * Contributes properties: componentNameExpander, markupWriterFactory, request
+     */
+    public static void contributeInfrastructure(
+            Configuration<InfrastructureContribution> configuration, ServiceLocator locator,
+            @InjectService("WebRequest")
+            WebRequest request, @InjectService("WebResponse")
+            WebResponse response, @InjectService("tapestry.ioc.TypeCoercer")
+            TypeCoercer typeCoercer)
+    {
+        add(configuration, locator, MarkupWriterFactory.class);
+        add(configuration, locator, PersistentFieldManager.class);
+        add(configuration, locator, Environment.class);
+        add(configuration, locator, ComponentSource.class);
+
+        configuration.add(new InfrastructureContribution("request", request));
+        configuration.add(new InfrastructureContribution("response", response));
+        configuration.add(new InfrastructureContribution("typeCoercer", typeCoercer));
+    }
+
+    /**
+     * Contributes the {@link ObjectProvider} provided by {@link Infrastructure#getObjectProvider()}
+     * mapped to the provider prefix "infrastructure".
+     */
+    @Contribute("tapestry.ioc.MasterObjectProvider")
+    public static void contributeInfrastructureToMasterObjectProvider(
+            MappedConfiguration<String, ObjectProvider> configuration,
+            @InjectService("Infrastructure")
+            Infrastructure infrastructure)
+    {
+        configuration.add("infrastructure", infrastructure.getObjectProvider());
+    }
+
+    public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration,
+            @InjectService("tapestry.internal.LinkFactory")
+            LinkFactory linkFactory)
+    {
+        configuration.add(
+                "HTML",
+                new PageRenderDispatcher(_pageResponseRenderer, _requestPageCache));
+
+        // This goes after the HTML one so that the "." in ".html" doesn't confuse it.
+
+        configuration.add("ComponentEvent", new ComponentEventDispatcher(_requestPageCache,
+                linkFactory), "after:HTML");
+    }
+
+    public static ComponentClassResolver buildComponentClassResolver(
+            @InjectService("tapestry.internal.ComponentInstantiatorSource")
+            ComponentInstantiatorSource source, Collection<LibraryMapping> configuration)
+    {
+        ComponentClassResolverImpl service = new ComponentClassResolverImpl(source, configuration);
+
+        // Allow the resolver to clean its cache when the source is invalidated
+
+        source.addInvalidationListener(service);
+
+        return service;
+    }
+
+    public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration)
+    {
+        configuration.add(new LibraryMapping("core", "org.apache.tapestry.corelib"));
+    }
+
+    public static BindingSource buildBindingSource(Map<String, BindingFactory> configuration)
+    {
+        return new BindingSourceImpl(configuration);
+    }
+
+    /** Contributes the factory for "literal:" bindings. */
+    public static void contributeBindingSource(
+            MappedConfiguration<String, BindingFactory> configuration,
+            @InjectService("tapestry.internal.PropBindingFactory")
+            BindingFactory propBindingFactory)
+    {
+        configuration.add(InternalConstants.LITERAL_BINDING_PREFIX, new LiteralBindingFactory());
+        configuration.add(InternalConstants.PROP_BINDING_PREFIX, propBindingFactory);
+    }
+
+    /**
+     * Returns a {@link ClassFactory} that can be used to create extra classes around component
+     * classes.
+     */
+    public static ClassFactory buildComponentClassFactory(Log log,
+            @InjectService("tapestry.internal.ComponentInstantiatorSource")
+            ComponentInstantiatorSource source)
+    {
+        return new ComponentClassFactoryImpl(log, source);
+    }
+
+    /**
+     * A chain of command for providing values for {@link org.apache.tapestry.annotations.Inject}-ed
+     * fields in component classes. The service's configuration can be extended to allow for
+     * different automatic injections (based on some combination of field type and field name).
+     */
+
+    public InjectionProvider buildInjectionProvider(List<InjectionProvider> configuration)
+    {
+        return _chainBuilder.build(InjectionProvider.class, configuration);
+    }
+
+    /**
+     * Contributes the elemental providers:
+     * <ul>
+     * <li>ComponentResources -- give component access to its resources</li>
+     * <li>Default -- looks for a unique IoC service that matches the field type</li>
+     * </ul>
+     */
+    public static void contributeInjectionProvider(
+            OrderedConfiguration<InjectionProvider> configuration)
+    {
+        configuration.add("ComponentResources", new ComponentResourcesInjectionProvider());
+        configuration.add("Default", new DefaultInjectionProvider(), "after:*.*");
+    }
+
+    /**
+     * Adds a number of standard component class transform workers:
+     * <ul>
+     * <li>Retain -- allows fields to retain their values between requests</li>
+     * <li>Persist -- allows fields to store their their value persistently between requests</li>
+     * <li>Parameter -- identifies parameters based on the
+     * {@link org.apache.tapestry.annotations.Parameter} annotation</li>
+     * <li>Component -- identifies embedded components based on the
+     * {@link org.apache.tapestry.annotations.Component} annotation</li>
+     * <li>Mixin -- adds a mixin as part of a component's implementation</li>
+     * <li>Environment -- allows fields to contain values extracted from the {@link Environment}
+     * service</li>
+     * <li>SupportsInformalParameters -- checks for the annotation</li>
+     * <li>UnclaimedField -- identifies unclaimed fields and resets them to null/0/false at the end
+     * of the request</li>
+     * <li>SetupRender, BeginRender, etc. -- correspond to component render phases and annotations</li>
+     * </ul>
+     */
+    public static void contributeComponentClassTransformWorker(
+            OrderedConfiguration<ComponentClassTransformWorker> configuration,
+            ServiceLocator locator, @InjectService("tapestry.ioc.MasterObjectProvider")
+            ObjectProvider objectProvider, @InjectService("InjectionProvider")
+            InjectionProvider injectionProvider, @InjectService("Environment")
+            Environment environment, @InjectService("tapestry.ComponentClassResolver")
+            ComponentClassResolver resolver)
+    {
+        // TODO: Proper scheduling of all of this. Since a given field or method should
+        // only have a single annotation, the order doesn't matter so much, as long as
+        // UnclaimedField is last.
+
+        configuration.add("Inject", new InjectWorker(objectProvider, locator, injectionProvider));
+        configuration.add("Parameter", new ParameterWorker());
+        configuration.add("Component", new ComponentWorker(resolver));
+        configuration.add("Environment", new EnvironmentalWorker(environment));
+        configuration.add("Mixin", new MixinWorker(resolver));
+        configuration.add("OnEvent", new OnEventWorker());
+        configuration.add("SupportsInformalParameters", new SupportsInformalParametersWorker());
+
+        // Workers for the component rendering state machine methods; this is in typical
+        // execution order.
+
+        add(configuration, TransformConstants.SETUP_RENDER_SIGNATURE, SetupRender.class, false);
+        add(
+                configuration,
+                TransformConstants.PRE_BEGIN_RENDER_SIGNATURE,
+                PreBeginRender.class,
+                false);
+        add(configuration, TransformConstants.BEGIN_RENDER_SIGNATURE, BeginRender.class, false);
+        add(
+                configuration,
+                TransformConstants.POST_BEGIN_RENDER_SIGNATURE,
+                PostBeginRender.class,
+                false);
+        add(
+                configuration,
+                TransformConstants.BEFORE_RENDER_TEMPLATE_SIGNATURE,
+                BeforeRenderTemplate.class,
+                false);
+        add(
+                configuration,
+                TransformConstants.BEFORE_RENDER_BODY_SIGNATURE,
+                BeforeRenderBody.class,
+                false);
+
+        // These phases operate in reverse order.
+
+        add(
+                configuration,
+                TransformConstants.AFTER_RENDER_BODY_SIGNATURE,
+                AfterRenderBody.class,
+                true);
+        add(
+                configuration,
+                TransformConstants.AFTER_RENDER_TEMPLATE_SIGNATURE,
+                AfterRenderTemplate.class,
+                true);
+        add(configuration, TransformConstants.AFTER_RENDER_SIGNATURE, AfterRender.class, true);
+        add(configuration, TransformConstants.CLEANUP_RENDER_SIGNATURE, CleanupRender.class, true);
+
+        configuration.add("Retain", new RetainWorker());
+        configuration.add("Persist", new PersistWorker());
+        configuration.add("UnclaimedField", new UnclaimedFieldWorker(), "after:*.*");
+    }
+
+    private static void add(OrderedConfiguration<ComponentClassTransformWorker> configuration,
+            MethodSignature signature, Class<? extends Annotation> annotationClass, boolean reverse)
+    {
+        // make the name match the annotation class name.
+
+        String name = IOCUtilities.toSimpleId(annotationClass.getName());
+
+        configuration.add(name, new ComponentLifecycleMethodWorker(signature, annotationClass,
+                reverse));
+    }
+
+    @Lifecycle("perthread")
+    public static Environment buildEnvironment()
+    {
+        return new EnvironmentImpl();
+    }
+
+    /**
+     * Controls setup and cleanup of the environment during page rendering (the generation of a
+     * markup stream response for the client web browser).
+     */
+    public PageRenderInitializer buildPageRenderInitializer(
+            final List<PageRenderCommand> configuration)
+    {
+        return new PageRenderInitializer()
+        {
+            public void setup()
+            {
+                _environment.clear();
+
+                for (PageRenderCommand command : configuration)
+                    command.setup(_environment);
+            }
+
+            public void cleanup()
+            {
+                Iterator<PageRenderCommand> i = InternalUtils.reverseIterator(configuration);
+
+                while (i.hasNext())
+                    i.next().cleanup(_environment);
+
+                // Probably not necessary, but what the heck!
+                _environment.clear();
+            }
+        };
+    }
+
+    public static void contributePageRenderInitializer(
+            OrderedConfiguration<PageRenderCommand> configuration)
+    {
+        // I'm not happy with this lifecycle, per-se. Instead of Runnable, I think we need an
+        // interface more tailored to Environment, with perhaps a second method for the end of
+        // the page render.
+
+        configuration.add("PageRenderSupport", new PageRenderCommand()
+        {
+            public void setup(Environment environment)
+            {
+                environment.push(PageRenderSupport.class, new PageRenderSupportImpl());
+            }
+
+            public void cleanup(Environment environment)
+            {
+                environment.pop(PageRenderSupport.class);
+            }
+        });
+
+        configuration.add("Heartbeat", new PageRenderCommand()
+        {
+            public void setup(Environment environment)
+            {
+                HeartbeatImpl heartbeat = new HeartbeatImpl();
+
+                heartbeat.begin();
+
+                environment.push(Heartbeat.class, heartbeat);
+            }
+
+            public void cleanup(Environment environment)
+            {
+                environment.pop(Heartbeat.class).end();
+            }
+        });
+    }
+
+    /** A public service since extensions may provide new persistent strategies. */
+    public static PersistentFieldManager buildPersistentFieldManager(
+            Map<String, PersistentFieldStrategy> configuration)
+    {
+        return new PersistentFieldManagerImpl(configuration);
+    }
+
+    /**
+     * Contributes the "session" strategy.
+     */
+    public void contributePersistentFieldManager(
+            MappedConfiguration<String, PersistentFieldStrategy> configuration)
+    {
+        configuration.add("session", new SessionPersistentFieldStrategy(_request));
+    }
+
+    public ComponentSource buildComponentSource(
+            @InjectService("tapestry.internal.RequestPageCache")
+            RequestPageCache pageCache)
+    {
+        return new ComponentSourceImpl(pageCache);
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/HeartbeatImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/HeartbeatImplTest.java?view=auto&rev=476282
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/HeartbeatImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/HeartbeatImplTest.java Fri Nov 17 11:51:39 2006
@@ -0,0 +1,89 @@
+// Copyright 2006 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.internal.services;
+
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.services.Heartbeat;
+import org.testng.annotations.Test;
+
+public class HeartbeatImplTest extends InternalBaseTestCase
+{
+    @Test
+    public void single_heartbeat()
+    {
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+
+        replay();
+
+        Heartbeat hb = new HeartbeatImpl();
+
+        hb.begin();
+
+        hb.defer(r1);
+        hb.defer(r2);
+
+        verify();
+
+        r1.run();
+        r2.run();
+
+        replay();
+
+        hb.end();
+
+        verify();
+    }
+
+    @Test
+    public void nested_heartbeats()
+    {
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+        Runnable r3 = newRunnable();
+
+        replay();
+
+        Heartbeat hb = new HeartbeatImpl();
+
+        hb.begin();
+
+        hb.defer(r1);
+        hb.defer(r2);
+
+        hb.begin();
+
+        hb.defer(r3);
+
+        verify();
+
+        r3.run();
+
+        replay();
+
+        hb.end();
+
+        verify();
+
+        r1.run();
+        r2.run();
+
+        replay();
+
+        hb.end();
+
+        verify();
+    }
+}