001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry; 016 017 import java.io.IOException; 018 import java.util.Locale; 019 020 import javax.servlet.ServletConfig; 021 import javax.servlet.ServletContext; 022 import javax.servlet.ServletException; 023 import javax.servlet.http.HttpServlet; 024 import javax.servlet.http.HttpServletRequest; 025 import javax.servlet.http.HttpServletResponse; 026 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 import org.apache.hivemind.ClassResolver; 030 import org.apache.hivemind.ErrorHandler; 031 import org.apache.hivemind.Registry; 032 import org.apache.hivemind.Resource; 033 import org.apache.hivemind.impl.DefaultClassResolver; 034 import org.apache.hivemind.impl.RegistryBuilder; 035 import org.apache.hivemind.impl.StrictErrorHandler; 036 import org.apache.hivemind.impl.XmlModuleDescriptorProvider; 037 import org.apache.hivemind.util.ContextResource; 038 import org.apache.tapestry.services.ApplicationInitializer; 039 import org.apache.tapestry.services.ServletRequestServicer; 040 import org.apache.tapestry.util.exception.ExceptionAnalyzer; 041 042 /** 043 * Links a servlet container with a Tapestry application. The servlet has some responsibilities 044 * related to bootstrapping the application (in terms of logging, reading the 045 * {@link ApplicationSpecification specification}, etc.). It is also responsible for creating or 046 * locating the {@link IEngine}and delegating incoming requests to it. 047 * <p> 048 * The servlet init parameter <code>org.apache.tapestry.specification-path</code> should be set to 049 * the complete resource path (within the classpath) to the application specification, i.e., 050 * <code>/com/foo/bar/MyApp.application</code>. 051 * <p> 052 * In some servlet containers (notably <a href="www.bea.com"/>WebLogic </a>) it is necessary to 053 * invoke {@link HttpSession#setAttribute(String,Object)}in order to force a persistent value to be 054 * replicated to the other servers in the cluster. Tapestry applications usually only have a single 055 * persistent value, the {@link IEngine engine}. For persistence to work in such an environment, 056 * the JVM system property <code>org.apache.tapestry.store-engine</code> must be set to 057 * <code>true</code>. This will force the application servlet to restore the engine into the 058 * {@link HttpSession}at the end of each request cycle. 059 * <p> 060 * As of release 1.0.1, it is no longer necessary for a {@link HttpSession}to be created on the 061 * first request cycle. Instead, the HttpSession is created as needed by the {@link IEngine}... 062 * that is, when a visit object is created, or when persistent page state is required. Otherwise, 063 * for sessionless requests, an {@link IEngine}from a {@link Pool}is used. Additional work must be 064 * done so that the {@link IEngine}can change locale <em>without</em> forcing the creation of a 065 * session; this involves the servlet and the engine storing locale information in a {@link Cookie}. 066 * <p> 067 * As of release 4.0, this servlet will also create a HiveMind Registry and manage it. 068 * 069 * @author Howard Lewis Ship 070 */ 071 072 public class ApplicationServlet extends HttpServlet 073 { 074 private static final long serialVersionUID = -8046042689991538059L; 075 076 /** 077 * Prefix used to store the HiveMind Registry into the ServletContext. This string is suffixed 078 * with the servlet name (in case multiple Tapestry applications are executing within a single 079 * web application). 080 * 081 * @since 4.0 082 */ 083 084 private static final String REGISTRY_KEY_PREFIX = "org.apache.tapestry.Registry:"; 085 086 private static final Log LOG = LogFactory.getLog(ApplicationServlet.class); 087 088 /** 089 * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}. 090 * 091 * @since 1.0.6 092 */ 093 094 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, 095 ServletException 096 { 097 doService(request, response); 098 } 099 100 /** 101 * @since 2.3 102 */ 103 104 private ClassResolver _resolver; 105 106 /** 107 * The key used to store the registry into the ServletContext. 108 * 109 * @since 4.0 110 */ 111 112 private String _registryKey; 113 114 /** 115 * @since 4.0 116 */ 117 118 private Registry _registry; 119 120 /** 121 * @since 4.0 122 */ 123 private ServletRequestServicer _requestServicer; 124 125 /** 126 * Handles the GET and POST requests. Performs the following: 127 * <ul> 128 * <li>Construct a {@link RequestContext} 129 * <li>Invoke {@link #getEngine(RequestContext)}to get or create the {@link IEngine} 130 * <li>Invoke {@link IEngine#service(RequestContext)}on the application 131 * </ul> 132 */ 133 134 protected void doService(HttpServletRequest request, HttpServletResponse response) 135 throws IOException, ServletException 136 { 137 try 138 { 139 _registry.setupThread(); 140 141 _requestServicer.service(request, response); 142 } 143 catch (ServletException ex) 144 { 145 log("ServletException", ex); 146 147 show(ex); 148 149 // Rethrow it. 150 151 throw ex; 152 } 153 catch (IOException ex) 154 { 155 log("IOException", ex); 156 157 show(ex); 158 159 // Rethrow it. 160 161 throw ex; 162 } 163 finally 164 { 165 _registry.cleanupThread(); 166 } 167 } 168 169 protected void show(Exception ex) 170 { 171 System.err.println("\n\n**********************************************************\n\n"); 172 173 new ExceptionAnalyzer().reportException(ex, System.err); 174 175 System.err.println("\n**********************************************************\n"); 176 177 } 178 179 /** 180 * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}. 181 */ 182 183 public void doPost(HttpServletRequest request, HttpServletResponse response) 184 throws IOException, ServletException 185 { 186 doService(request, response); 187 } 188 189 /** 190 * Reads the application specification when the servlet is first initialized. All 191 * {@link IEngine engine instances}will have access to the specification via the servlet. 192 * 193 * @see #constructApplicationSpecification() 194 * @see #createResourceResolver() 195 */ 196 197 public void init(ServletConfig config) throws ServletException 198 { 199 String name = config.getServletName(); 200 201 _registryKey = REGISTRY_KEY_PREFIX + name; 202 203 long startTime = System.currentTimeMillis(); 204 long elapsedToRegistry = 0; 205 206 super.init(config); 207 208 _resolver = createClassResolver(); 209 210 try 211 { 212 _registry = constructRegistry(config); 213 214 elapsedToRegistry = System.currentTimeMillis() - startTime; 215 216 initializeApplication(); 217 218 config.getServletContext().setAttribute(_registryKey, _registry); 219 } 220 catch (Exception ex) 221 { 222 show(ex); 223 224 throw new ServletException(TapestryMessages.servletInitFailure(ex), ex); 225 } 226 227 long elapsedOverall = System.currentTimeMillis() - startTime; 228 229 LOG.info(TapestryMessages.servletInit(name, elapsedToRegistry, elapsedOverall)); 230 } 231 232 /** 233 * Invoked from {@link #init(ServletConfig)}to create a resource resolver for the servlet 234 * (which will utlimately be shared and used through the application). 235 * <p> 236 * This implementation constructs a {@link DefaultResourceResolver}, subclasses may provide a 237 * different implementation. 238 * 239 * @see #getResourceResolver() 240 * @since 2.3 241 */ 242 243 protected ClassResolver createClassResolver() 244 { 245 return new DefaultClassResolver(); 246 } 247 248 /** 249 * Invoked from {@link #init(ServletConfig)}to construct the Registry to be used by the 250 * application. 251 * <p> 252 * This looks in the standard places (on the classpath), but also in the WEB-INF/name and 253 * WEB-INF folders (where name is the name of the servlet). 254 * 255 * @since 4.0 256 */ 257 protected Registry constructRegistry(ServletConfig config) 258 { 259 ErrorHandler errorHandler = constructErrorHandler(config); 260 261 RegistryBuilder builder = new RegistryBuilder(errorHandler); 262 263 builder.addModuleDescriptorProvider(new XmlModuleDescriptorProvider(_resolver)); 264 265 String name = config.getServletName(); 266 ServletContext context = config.getServletContext(); 267 268 addModuleIfExists(builder, context, "/WEB-INF/" + name + "/hivemodule.xml"); 269 addModuleIfExists(builder, context, "/WEB-INF/hivemodule.xml"); 270 271 return builder.constructRegistry(Locale.getDefault()); 272 } 273 274 /** 275 * Invoked by {@link #constructRegistry(ServletConfig)} to create and return an 276 * {@link ErrorHandler} instance to be used when constructing the Registry (and then to handle 277 * any runtime exceptions). This implementation returns a new instance of 278 * {@link org.apache.hivemind.impl.StrictErrorHandler}. 279 * 280 * @since 4.0 281 */ 282 protected ErrorHandler constructErrorHandler(ServletConfig config) 283 { 284 return new StrictErrorHandler(); 285 } 286 287 /** 288 * Looks for a file in the servlet context; if it exists, it is expected to be a HiveMind module 289 * descriptor, and is added to the builder. 290 * 291 * @since 4.0 292 */ 293 294 protected void addModuleIfExists(RegistryBuilder builder, ServletContext context, String path) 295 { 296 Resource r = new ContextResource(context, path); 297 298 if (r.getResourceURL() == null) 299 return; 300 301 builder.addModuleDescriptorProvider(new XmlModuleDescriptorProvider(_resolver, r)); 302 } 303 304 /** 305 * Invoked from {@link #init(ServletConfig)}, after the registry has been constructed, to 306 * bootstrap the application via the <code>tapestry.MasterApplicationInitializer</code> 307 * service. 308 * 309 * @since 4.0 310 */ 311 protected void initializeApplication() 312 { 313 ApplicationInitializer ai = (ApplicationInitializer) _registry.getService( 314 "tapestry.init.MasterInitializer", 315 ApplicationInitializer.class); 316 317 ai.initialize(this); 318 319 _registry.cleanupThread(); 320 321 _requestServicer = (ServletRequestServicer) _registry.getService( 322 "tapestry.request.ServletRequestServicer", 323 ServletRequestServicer.class); 324 } 325 326 /** 327 * Shuts down the registry (if it exists). 328 * 329 * @since 4.0 330 */ 331 public void destroy() 332 { 333 getServletContext().removeAttribute(_registryKey); 334 335 if (_registry != null) 336 { 337 _registry.shutdown(); 338 _registry = null; 339 } 340 } 341 }