    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.modules;
    014
    015import java.io.IOException;
    016import java.util.List;
    017import java.util.Map;
    018
    019import javax.servlet.ServletContext;
    020import javax.servlet.http.HttpServletRequest;
    021import javax.servlet.http.HttpServletResponse;
    022
    023import org.apache.tapestry5.commons.MappedConfiguration;
    024import org.apache.tapestry5.commons.OrderedConfiguration;
    025import org.apache.tapestry5.http.OptimizedSessionPersistedObject;
    026import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
    027import org.apache.tapestry5.http.internal.gzip.GZipFilter;
    028import org.apache.tapestry5.http.internal.services.ApplicationGlobalsImpl;
    029import org.apache.tapestry5.http.internal.services.BaseURLSourceImpl;
    030import org.apache.tapestry5.http.internal.services.ContextImpl;
    031import org.apache.tapestry5.http.internal.services.DefaultSessionPersistedObjectAnalyzer;
    032import org.apache.tapestry5.http.internal.services.OptimizedSessionPersistedObjectAnalyzer;
    033import org.apache.tapestry5.http.internal.services.RequestGlobalsImpl;
    034import org.apache.tapestry5.http.internal.services.RequestImpl;
    035import org.apache.tapestry5.http.internal.services.ResponseCompressionAnalyzerImpl;
    036import org.apache.tapestry5.http.internal.services.ResponseImpl;
    037import org.apache.tapestry5.http.internal.services.TapestrySessionFactory;
    038import org.apache.tapestry5.http.internal.services.TapestrySessionFactoryImpl;
    039import org.apache.tapestry5.http.services.ApplicationGlobals;
    040import org.apache.tapestry5.http.services.ApplicationInitializer;
    041import org.apache.tapestry5.http.services.ApplicationInitializerFilter;
    042import org.apache.tapestry5.http.services.BaseURLSource;
    043import org.apache.tapestry5.http.services.Context;
    044import org.apache.tapestry5.http.services.Dispatcher;
    045import org.apache.tapestry5.http.services.HttpServletRequestFilter;
    046import org.apache.tapestry5.http.services.HttpServletRequestHandler;
    047import org.apache.tapestry5.http.services.Request;
    048import org.apache.tapestry5.http.services.RequestFilter;
    049import org.apache.tapestry5.http.services.RequestGlobals;
    050import org.apache.tapestry5.http.services.RequestHandler;
    051import org.apache.tapestry5.http.services.Response;
    052import org.apache.tapestry5.http.services.ResponseCompressionAnalyzer;
    053import org.apache.tapestry5.http.services.ServletApplicationInitializer;
    054import org.apache.tapestry5.http.services.ServletApplicationInitializerFilter;
    055import org.apache.tapestry5.http.services.SessionPersistedObjectAnalyzer;
    056import org.apache.tapestry5.ioc.ServiceBinder;
    057import org.apache.tapestry5.ioc.annotations.Autobuild;
    058import org.apache.tapestry5.ioc.annotations.Marker;
    059import org.apache.tapestry5.ioc.annotations.Primary;
    060import org.apache.tapestry5.ioc.annotations.Symbol;
    061import org.apache.tapestry5.ioc.services.ChainBuilder;
    062import org.apache.tapestry5.ioc.services.PipelineBuilder;
    063import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
    064import org.apache.tapestry5.ioc.services.StrategyBuilder;
    065import org.slf4j.Logger;
    066
    067/**
    068 * The Tapestry module for HTTP handling classes.
    069 */
    070public final class TapestryHttpModule {
    071
    072    final private PropertyShadowBuilder shadowBuilder;
    073    final private RequestGlobals requestGlobals;
    074    final private PipelineBuilder pipelineBuilder;
    075    final private ApplicationGlobals applicationGlobals;
    076
    077    public TapestryHttpModule(PropertyShadowBuilder shadowBuilder,
    078            RequestGlobals requestGlobals, PipelineBuilder pipelineBuilder,
    079            ApplicationGlobals applicationGlobals)
    080    {
    081        this.shadowBuilder = shadowBuilder;
    082        this.requestGlobals = requestGlobals;
    083        this.pipelineBuilder = pipelineBuilder;
    084        this.applicationGlobals = applicationGlobals;
    085    }
    086
    087    public static void bind(ServiceBinder binder)
    088    {
    089        binder.bind(RequestGlobals.class, RequestGlobalsImpl.class);
    090        binder.bind(ApplicationGlobals.class, ApplicationGlobalsImpl.class);
    091        binder.bind(TapestrySessionFactory.class, TapestrySessionFactoryImpl.class);
    092        binder.bind(BaseURLSource.class, BaseURLSourceImpl.class);
    093        binder.bind(ResponseCompressionAnalyzer.class, ResponseCompressionAnalyzerImpl.class);
    094    }
    095
    096    /**
    097     * Contributes factory defaults that may be overridden.
    098     */
    099    public static void contributeFactoryDefaults(MappedConfiguration<String, Object> configuration)
    100    {
    101        configuration.add(TapestryHttpSymbolConstants.SESSION_LOCKING_ENABLED, true);
    102        configuration.add(TapestryHttpSymbolConstants.CLUSTERED_SESSIONS, true);
    103        configuration.add(TapestryHttpSymbolConstants.CHARSET, "UTF-8");
    104        configuration.add(TapestryHttpSymbolConstants.APPLICATION_VERSION, "0.0.1");
    105        configuration.add(TapestryHttpSymbolConstants.GZIP_COMPRESSION_ENABLED, true);
    106        configuration.add(TapestryHttpSymbolConstants.MIN_GZIP_SIZE, 100);
    107
    108        // The default values denote "use values from request"
    109        configuration.add(TapestryHttpSymbolConstants.HOSTNAME, "");
    110        configuration.add(TapestryHttpSymbolConstants.HOSTPORT, 0);
    111        configuration.add(TapestryHttpSymbolConstants.HOSTPORT_SECURE, 0);
    112    }
    113
    114    /**
    115     * Builds a shadow of the RequestGlobals.request property. Note again that
    116     * the shadow can be an ordinary singleton,
    117     * even though RequestGlobals is perthread.
    118     */
    119    public Request buildRequest(PropertyShadowBuilder shadowBuilder)
    120    {
    121        return shadowBuilder.build(requestGlobals, "request", Request.class);
    122    }
    123
    124    /**
    125     * Builds a shadow of the RequestGlobals.HTTPServletRequest property.
    126     * Generally, you should inject the {@link Request} service instead, as
    127     * future version of Tapestry may operate beyond just the servlet API.
    128     */
    129    public HttpServletRequest buildHttpServletRequest()
    130    {
    131        return shadowBuilder.build(requestGlobals, "HTTPServletRequest", HttpServletRequest.class);
    132    }
    133
    134    /**
    135     * @since 5.1.0.0
    136     */
    137    public HttpServletResponse buildHttpServletResponse()
    138    {
    139        return shadowBuilder.build(requestGlobals, "HTTPServletResponse", HttpServletResponse.class);
    140    }
    141
    142    /**
    143     * Builds a shadow of the RequestGlobals.response property. Note again that
    144     * the shadow can be an ordinary singleton,
    145     * even though RequestGlobals is perthread.
    146     */
    147    public Response buildResponse()
    148    {
    149        return shadowBuilder.build(requestGlobals, "response", Response.class);
    150    }
    151
    152    /**
    153     * Ordered contributions to the MasterDispatcher service allow different URL
    154     * matching strategies to occur.
    155     */
    156    @Marker(Primary.class)
    157    public Dispatcher buildMasterDispatcher(List<Dispatcher> configuration,
    158            ChainBuilder chainBuilder)
    159    {
    160        return chainBuilder.build(Dispatcher.class, configuration);
    161    }
    162
    163    /**
    164     * The master SessionPersistedObjectAnalyzer.
    165     *
    166     * @since 5.1.0.0
    167     */
    168    @SuppressWarnings("rawtypes")
    169    @Marker(Primary.class)
    170    public SessionPersistedObjectAnalyzer buildSessionPersistedObjectAnalyzer(
    171            Map<Class, SessionPersistedObjectAnalyzer> configuration,
    172            StrategyBuilder strategyBuilder)
    173    {
    174        return strategyBuilder.build(SessionPersistedObjectAnalyzer.class, configuration);
    175    }
    176
    177    /**
    178     * Identifies String, Number and Boolean as immutable objects, a catch-all
    179     * handler for Object (that understands
    180     * the {@link org.apache.tapestry5.http.annotations.ImmutableSessionPersistedObject} annotation),
    181     * and a handler for {@link org.apache.tapestry5.http.OptimizedSessionPersistedObject}.
    182     *
    183     * @since 5.1.0.0
    184     */
    185    @SuppressWarnings("rawtypes")
    186    public static void contributeSessionPersistedObjectAnalyzer(
    187            MappedConfiguration<Class, SessionPersistedObjectAnalyzer> configuration)
    188    {
    189        configuration.add(Object.class, new DefaultSessionPersistedObjectAnalyzer());
    190
    191        SessionPersistedObjectAnalyzer<Object> immutable = new SessionPersistedObjectAnalyzer<Object>()
    192        {
    193            public boolean checkAndResetDirtyState(Object sessionPersistedObject)
    194            {
    195                return false;
    196            }
    197        };
    198
    199        configuration.add(String.class, immutable);
    200        configuration.add(Number.class, immutable);
    201        configuration.add(Boolean.class, immutable);
    202
    203        configuration.add(OptimizedSessionPersistedObject.class, new OptimizedSessionPersistedObjectAnalyzer());
    204    }
    205
    206    /**
    207     * Initializes the application, using a pipeline of {@link org.apache.tapestry5.http.services.ApplicationInitializer}s.
    208     */
    209    @Marker(Primary.class)
    210    public ApplicationInitializer buildApplicationInitializer(Logger logger,
    211                                                              List<ApplicationInitializerFilter> configuration)
    212    {
    213        ApplicationInitializer terminator = new ApplicationInitializerTerminator();
    214
    215        return pipelineBuilder.build(logger, ApplicationInitializer.class, ApplicationInitializerFilter.class,
    216                configuration, terminator);
    217    }
    218
    219    public HttpServletRequestHandler buildHttpServletRequestHandler(Logger logger,
    220
    221                                                                    List<HttpServletRequestFilter> configuration,
    222
    223                                                                    @Primary
    224                                                                    RequestHandler handler,
    225
    226                                                                    @Symbol(TapestryHttpSymbolConstants.CHARSET)
    227                                                                    String applicationCharset,
    228
    229                                                                    TapestrySessionFactory sessionFactory)
    230    {
    231        HttpServletRequestHandler terminator = new HttpServletRequestHandlerTerminator(handler, applicationCharset,
    232                sessionFactory);
    233
    234        return pipelineBuilder.build(logger, HttpServletRequestHandler.class, HttpServletRequestFilter.class,
    235                configuration, terminator);
    236    }
    237
    238    @Marker(Primary.class)
    239    public RequestHandler buildRequestHandler(Logger logger, List<RequestFilter> configuration,
    240
    241                                              @Primary
    242                                              Dispatcher masterDispatcher)
    243    {
    244        RequestHandler terminator = new RequestHandlerTerminator(masterDispatcher);
    245
    246        return pipelineBuilder.build(logger, RequestHandler.class, RequestFilter.class, configuration, terminator);
    247    }
    248
    249    public ServletApplicationInitializer buildServletApplicationInitializer(Logger logger,
    250                                                                            List<ServletApplicationInitializerFilter> configuration,
    251
    252                                                                            @Primary
    253                                                                            ApplicationInitializer initializer)
    254    {
    255        ServletApplicationInitializer terminator = new ServletApplicationInitializerTerminator(initializer);
    256
    257        return pipelineBuilder.build(logger, ServletApplicationInitializer.class,
    258                ServletApplicationInitializerFilter.class, configuration, terminator);
    259    }
    260
    261    /**
    262     * <dl>
    263     * <dt>StoreIntoGlobals</dt>
    264     * <dd>Stores the request and response into {@link org.apache.tapestry5.http.services.RequestGlobals} at the start of the
    265     * pipeline</dd>
    266     * <dt>IgnoredPaths</dt>
    267     * <dd>Identifies requests that are known (via the IgnoredPathsFilter service's configuration) to be mapped to other
    268     * applications</dd>
    269     * <dt>GZip</dt>
    270     * <dd>Handles GZIP compression of response streams (if supported by client)</dd>
    271     * </dl>
    272     */
    273    public void contributeHttpServletRequestHandler(OrderedConfiguration<HttpServletRequestFilter> configuration,
    274            @Symbol(TapestryHttpSymbolConstants.GZIP_COMPRESSION_ENABLED) boolean gzipCompressionEnabled,
    275            @Autobuild GZipFilter gzipFilter)
    276    {
    277
    278        HttpServletRequestFilter storeIntoGlobals = new HttpServletRequestFilter()
    279        {
    280            public boolean service(HttpServletRequest request, HttpServletResponse response,
    281                                   HttpServletRequestHandler handler) throws IOException
    282            {
    283                requestGlobals.storeServletRequestResponse(request, response);
    284
    285                return handler.service(request, response);
    286            }
    287        };
    288
    289        configuration.add("StoreIntoGlobals", storeIntoGlobals, "before:*");
    290
    291        configuration.add("GZIP", gzipCompressionEnabled ? gzipFilter : null);
    292
    293    }
    294
    295
    296    // A bunch of classes "promoted" from inline inner class to nested classes,
    297    // just so that the stack trace would be more readable. Most of these
    298    // are terminators for pipeline services.
    299
    300    /**
    301     * @since 5.1.0.0
    302     */
    303    private class ApplicationInitializerTerminator implements ApplicationInitializer
    304    {
    305        public void initializeApplication(Context context)
    306        {
    307            applicationGlobals.storeContext(context);
    308        }
    309    }
    310
    311    /**
    312     * @since 5.1.0.0
    313     */
    314    private class HttpServletRequestHandlerTerminator implements HttpServletRequestHandler
    315    {
    316        private final RequestHandler handler;
    317        private final String applicationCharset;
    318        private final TapestrySessionFactory sessionFactory;
    319
    320        public HttpServletRequestHandlerTerminator(RequestHandler handler, String applicationCharset,
    321                                                   TapestrySessionFactory sessionFactory)
    322        {
    323            this.handler = handler;
    324            this.applicationCharset = applicationCharset;
    325            this.sessionFactory = sessionFactory;
    326        }
    327
    328        public boolean service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
    329                throws IOException
    330        {
    331            requestGlobals.storeServletRequestResponse(servletRequest, servletResponse);
    332
    333            // Should have started doing this a long time ago: recoding attributes into
    334            // the request for things that may be needed downstream, without having to extend
    335            // Request.
    336
    337            servletRequest.setAttribute("servletAPI.protocol", servletRequest.getProtocol());
    338            servletRequest.setAttribute("servletAPI.characterEncoding", servletRequest.getCharacterEncoding());
    339            servletRequest.setAttribute("servletAPI.contentLength", servletRequest.getContentLength());
    340            servletRequest.setAttribute("servletAPI.authType", servletRequest.getAuthType());
    341            servletRequest.setAttribute("servletAPI.contentType", servletRequest.getContentType());
    342            servletRequest.setAttribute("servletAPI.scheme", servletRequest.getScheme());
    343
    344            Request request = new RequestImpl(servletRequest, applicationCharset, sessionFactory);
    345            Response response = new ResponseImpl(servletRequest, servletResponse);
    346
    347            // TAP5-257: Make sure that the "initial guess" for request/response
    348            // is available, even ifsome filter in the RequestHandler pipeline replaces them.
    349            // Which just goes to show that there should have been only one way to access the Request/Response:
    350            // either functionally (via parameters) or global (via ReqeuestGlobals) but not both.
    351            // That ship has sailed.
    352
    353            requestGlobals.storeRequestResponse(request, response);
    354
    355            // Transition from the Servlet API-based pipeline, to the
    356            // Tapestry-based pipeline.
    357
    358            return handler.service(request, response);
    359        }
    360    }
    361
    362    /**
    363     * @since 5.1.0.0
    364     */
    365    private class RequestHandlerTerminator implements RequestHandler
    366    {
    367        private final Dispatcher masterDispatcher;
    368
    369        public RequestHandlerTerminator(Dispatcher masterDispatcher)
    370        {
    371            this.masterDispatcher = masterDispatcher;
    372        }
    373
    374        public boolean service(Request request, Response response) throws IOException
    375        {
    376            // Update RequestGlobals with the current request/response (in case
    377            // some filter replaced the
    378            // normal set).
    379            requestGlobals.storeRequestResponse(request, response);
    380
    381            return masterDispatcher.dispatch(request, response);
    382        }
    383    }
    384
    385    /**
    386     * @since 5.1.0.0
    387     */
    388    private class ServletApplicationInitializerTerminator implements ServletApplicationInitializer
    389    {
    390        private final ApplicationInitializer initializer;
    391
    392        public ServletApplicationInitializerTerminator(ApplicationInitializer initializer)
    393        {
    394            this.initializer = initializer;
    395        }
    396
    397        public void initializeApplication(ServletContext servletContext)
    398        {
    399            applicationGlobals.storeServletContext(servletContext);
    400
    401            // And now, down the (Web) ApplicationInitializer pipeline ...
    402
    403            ContextImpl context = new ContextImpl(servletContext);
    404
    405            applicationGlobals.storeContext(context);
    406
    407            initializer.initializeApplication(context);
    408        }
    409    }
    410
    411}