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}