    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}