001// Copyright 2006-2013 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5;
016
017import org.apache.tapestry5.internal.ServletContextSymbolProvider;
018import org.apache.tapestry5.internal.SingleKeySymbolProvider;
019import org.apache.tapestry5.internal.TapestryAppInitializer;
020import org.apache.tapestry5.internal.util.DelegatingSymbolProvider;
021import org.apache.tapestry5.ioc.Registry;
022import org.apache.tapestry5.ioc.def.ModuleDef;
023import org.apache.tapestry5.ioc.internal.services.SystemPropertiesSymbolProvider;
024import org.apache.tapestry5.ioc.services.SymbolProvider;
025import org.apache.tapestry5.services.HttpServletRequestHandler;
026import org.apache.tapestry5.services.ServletApplicationInitializer;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import javax.servlet.*;
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletResponse;
033import java.io.IOException;
034
035/**
036 * The TapestryFilter is responsible for intercepting all requests into the web application. It
037 * identifies the requests
038 * that are relevant to Tapestry, and lets the servlet container handle the rest. It is also
039 * responsible for
040 * initializing Tapestry.
041 * <p/>
042 * The application is primarily configured via context-level init parameters.
043 * <p/>
044 * <dl>
045 * <dt>tapestry.app-package</dt>
046 * <dd>The application package (used to search for pages, components, etc.)</dd>
047 * </dl>
048 * <p/>
049 * In addition, a JVM system property affects configuration: <code>tapestry.execution-mode</code>
050 * (with default value "production"). This property is a comma-separated list of execution modes.
051 * For each mode, an additional init parameter is checked for:
052 * <code>tapestry.<em>mode</em>-modules</code>; this is a comma-separated list of module class names
053 * to load. In this way, more precise control over the available modules can be obtained which is
054 * often needed during testing.
055 */
056public class TapestryFilter implements Filter
057{
058    private final Logger logger = LoggerFactory.getLogger(TapestryFilter.class);
059
060    private FilterConfig config;
061
062    private Registry registry;
063
064    private HttpServletRequestHandler handler;
065
066    /**
067     * Key under which the Tapestry IoC {@link org.apache.tapestry5.ioc.Registry} is stored in the
068     * ServletContext. This
069     * allows other code, beyond Tapestry, to obtain the Registry and, from it, any Tapestry
070     * services. Such code should
071     * be careful about invoking {@link org.apache.tapestry5.ioc.Registry#cleanupThread()}
072     * appropriately.
073     */
074    public static final String REGISTRY_CONTEXT_NAME = "org.apache.tapestry5.application-registry";
075
076    /**
077     * Initializes the filter using the {@link TapestryAppInitializer}. The application name is the
078     * capitalization of
079     * the filter name (as specified in web.xml).
080     */
081    public final void init(FilterConfig filterConfig) throws ServletException
082    {
083        config = filterConfig;
084
085        final ServletContext context = config.getServletContext();
086
087        String filterName = config.getFilterName();
088
089        SymbolProvider combinedProvider = new DelegatingSymbolProvider(
090                new SystemPropertiesSymbolProvider(),
091                new SingleKeySymbolProvider(SymbolConstants.CONTEXT_PATH, context.getContextPath()),
092                new ServletContextSymbolProvider(context),
093                new SingleKeySymbolProvider(SymbolConstants.EXECUTION_MODE, "production"));
094
095        String executionMode = combinedProvider.valueForSymbol(SymbolConstants.EXECUTION_MODE);
096
097        TapestryAppInitializer appInitializer = new TapestryAppInitializer(logger, combinedProvider,
098                filterName, executionMode);
099
100        appInitializer.addModules(provideExtraModuleDefs(context));
101        appInitializer.addModules(provideExtraModuleClasses(context));
102
103        registry = appInitializer.createRegistry();
104
105        context.setAttribute(REGISTRY_CONTEXT_NAME, registry);
106
107        ServletApplicationInitializer ai = registry.getService("ServletApplicationInitializer",
108                ServletApplicationInitializer.class);
109
110        ai.initializeApplication(context);
111
112        registry.performRegistryStartup();
113
114        handler = registry.getService("HttpServletRequestHandler", HttpServletRequestHandler.class);
115
116        init(registry);
117
118        appInitializer.announceStartup();
119
120        registry.cleanupThread();
121    }
122
123    protected final FilterConfig getFilterConfig()
124    {
125        return config;
126    }
127
128    /**
129     * Invoked from {@link #init(FilterConfig)} after the Registry has been created, to allow any
130     * additional
131     * initialization to occur. This implementation does nothing, and my be overridden in subclasses.
132     *
133     * @param registry
134     *         from which services may be extracted
135     * @throws ServletException
136     */
137    protected void init(Registry registry) throws ServletException
138    {
139
140    }
141
142    /**
143     * Overridden in subclasses to provide additional module definitions beyond those normally
144     * located. This
145     * implementation returns an empty array.
146     */
147    protected ModuleDef[] provideExtraModuleDefs(ServletContext context)
148    {
149        return new ModuleDef[0];
150    }
151
152    /**
153     * Overridden in subclasses to provide additional module classes beyond those normally located. This implementation
154     * returns an empty array.
155     *
156     * @since 5.3
157     */
158    protected Class[] provideExtraModuleClasses(ServletContext context)
159    {
160        return new Class[0];
161    }
162
163    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
164            throws IOException, ServletException
165    {
166        try
167        {
168            boolean handled = handler.service((HttpServletRequest) request,
169                    (HttpServletResponse) response);
170
171            if (!handled)
172            {
173                chain.doFilter(request, response);
174            }
175        } finally
176        {
177            registry.cleanupThread();
178        }
179    }
180
181    /**
182     * Shuts down and discards the registry. Invokes
183     * {@link #destroy(org.apache.tapestry5.ioc.Registry)} to allow
184     * subclasses to perform any shutdown logic, then shuts down the registry, and removes it from
185     * the ServletContext.
186     */
187    public final void destroy()
188    {
189        destroy(registry);
190
191        registry.shutdown();
192
193        config.getServletContext().removeAttribute(REGISTRY_CONTEXT_NAME);
194
195        registry = null;
196        config = null;
197        handler = null;
198    }
199
200    /**
201     * Invoked from {@link #destroy()} to allow subclasses to add additional shutdown logic to the
202     * filter. The Registry
203     * will be shutdown after this call. This implementation does nothing, and may be overridden in
204     * subclasses.
205     *
206     * @param registry
207     */
208    protected void destroy(Registry registry)
209    {
210
211    }
212}