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