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}