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}