001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.i18n.CmsMessageContainer; 035import org.opencms.site.CmsSite; 036import org.opencms.staticexport.CmsStaticExportData; 037import org.opencms.staticexport.CmsStaticExportRequest; 038import org.opencms.util.CmsRequestUtil; 039import org.opencms.util.CmsStringUtil; 040 041import java.io.IOException; 042 043import javax.servlet.ServletConfig; 044import javax.servlet.ServletException; 045import javax.servlet.http.HttpServlet; 046import javax.servlet.http.HttpServletRequest; 047import javax.servlet.http.HttpServletResponse; 048 049import org.apache.commons.logging.Log; 050 051/** 052 * This the main servlet of the OpenCms system.<p> 053 * 054 * From here, all operations that are results of HTTP requests are invoked. 055 * Any incoming request is handled in multiple steps: 056 * 057 * <ol><li>The requesting <code>{@link org.opencms.file.CmsUser}</code> is authenticated 058 * and a <code>{@link org.opencms.file.CmsObject}</code> with this users context information 059 * is created. This <code>{@link org.opencms.file.CmsObject}</code> is used to access all functions of OpenCms, limited by 060 * the authenticated users permissions. If the user is not identified, it is set to the default user, usually named "Guest".</li> 061 * 062 * <li>The requested <code>{@link org.opencms.file.CmsResource}</code> is loaded into OpenCms and depending on its type 063 * (and the users persmissions to display or modify it), 064 * it is send to one of the OpenCms <code>{@link org.opencms.loader.I_CmsResourceLoader}</code> implementations 065 * do be processed.</li> 066 * 067 * <li> 068 * The <code>{@link org.opencms.loader.I_CmsResourceLoader}</code> will then decide what to do with the 069 * contents of the requested <code>{@link org.opencms.file.CmsResource}</code>. 070 * In case of a JSP resource the JSP handling mechanism is invoked with the <code>{@link org.opencms.loader.CmsJspLoader}</code>, 071 * in case of an image (or another static resource) this will be returned by the <code>{@link org.opencms.loader.CmsDumpLoader}</code> 072 * etc. 073 * </li></ol> 074 * 075 * @since 6.0.0 076 * 077 * @see org.opencms.main.CmsShell 078 * @see org.opencms.file.CmsObject 079 * @see org.opencms.main.OpenCms 080 */ 081public class OpenCmsServlet extends HttpServlet implements I_CmsRequestHandler { 082 083 /** The current request in a threadlocal. */ 084 public static final ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<HttpServletRequest>(); 085 086 /** GWT RPC services suffix. */ 087 public static final String HANDLE_GWT = ".gwt"; 088 089 /** Handler prefix. */ 090 public static final String HANDLE_PATH = "/handle"; 091 092 /** Name of the <code>DefaultWebApplication</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */ 093 public static final String SERVLET_PARAM_DEFAULT_WEB_APPLICATION = "DefaultWebApplication"; 094 095 /** Name of the <code>OpenCmsHome</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */ 096 public static final String SERVLET_PARAM_OPEN_CMS_HOME = "OpenCmsHome"; 097 098 /** Name of the <code>OpenCmsServlet</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */ 099 public static final String SERVLET_PARAM_OPEN_CMS_SERVLET = "OpenCmsServlet"; 100 101 /** Name of the <code>WebApplicationContext</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */ 102 public static final String SERVLET_PARAM_WEB_APPLICATION_CONTEXT = "WebApplicationContext"; 103 104 /** Path to handler "error page" files in the VFS. */ 105 private static final String HANDLE_VFS_PATH = "/system/handler" + HANDLE_PATH; 106 107 /** Handler "error page" file suffix. */ 108 private static final String HANDLE_VFS_SUFFIX = ".html"; 109 110 /** Handler implementation names. */ 111 private static final String[] HANDLER_NAMES = {"404"}; 112 113 /** The log object for this class. */ 114 private static final Log LOG = CmsLog.getLog(OpenCmsServlet.class); 115 116 /** Serial version UID required for safe serialization. */ 117 private static final long serialVersionUID = 4729951599966070050L; 118 119 /** 120 * OpenCms servlet main request handling method.<p> 121 * 122 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 123 */ 124 @Override 125 public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { 126 127 currentRequest.set(req); 128 try { 129 130 // check to OpenCms runlevel 131 int runlevel = OpenCmsCore.getInstance().getRunLevel(); 132 133 // write OpenCms server identification in the response header 134 res.setHeader(CmsRequestUtil.HEADER_SERVER, OpenCmsCore.getInstance().getSystemInfo().getVersion()); 135 136 if (runlevel != OpenCms.RUNLEVEL_4_SERVLET_ACCESS) { 137 // not the "normal" servlet runlevel 138 if (runlevel == OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 139 // we have shell runlevel only, upgrade to servlet runlevel (required after setup wizard) 140 init(getServletConfig()); 141 } else { 142 // illegal runlevel, we can't process requests 143 // sending status code 403, indicating the server understood the request but refused to fulfill it 144 res.sendError(HttpServletResponse.SC_FORBIDDEN); 145 // goodbye 146 return; 147 } 148 } 149 150 String path = OpenCmsCore.getInstance().getPathInfo(req); 151 if (path.startsWith(HANDLE_PATH)) { 152 // this is a request to an OpenCms handler URI 153 invokeHandler(req, res); 154 } else if (path.endsWith(HANDLE_GWT)) { 155 // handle GWT rpc services 156 String serviceName = CmsResource.getName(path); 157 serviceName = serviceName.substring(0, serviceName.length() - HANDLE_GWT.length()); 158 OpenCmsCore.getInstance().invokeGwtService(serviceName, req, res, getServletConfig()); 159 } else { 160 // standard request to a URI in the OpenCms VFS 161 OpenCmsCore.getInstance().showResource(req, res); 162 } 163 } finally { 164 currentRequest.remove(); 165 } 166 } 167 168 /** 169 * OpenCms servlet POST request handling method, 170 * will just call {@link #doGet(HttpServletRequest, HttpServletResponse)}.<p> 171 * 172 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 173 */ 174 @Override 175 public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { 176 177 doGet(req, res); 178 } 179 180 /** 181 * @see org.opencms.main.I_CmsRequestHandler#getHandlerNames() 182 */ 183 public String[] getHandlerNames() { 184 185 return HANDLER_NAMES; 186 } 187 188 /** 189 * @see org.opencms.main.I_CmsRequestHandler#handle(HttpServletRequest, HttpServletResponse, String) 190 */ 191 public void handle(HttpServletRequest req, HttpServletResponse res, String name) 192 throws IOException, ServletException { 193 194 int errorCode; 195 try { 196 errorCode = Integer.valueOf(name).intValue(); 197 } catch (NumberFormatException nf) { 198 res.sendError(HttpServletResponse.SC_FORBIDDEN); 199 LOG.debug("Error parsing handler name.", nf); 200 return; 201 } 202 switch (errorCode) { 203 case 404: 204 CmsObject cms = null; 205 CmsStaticExportData exportData = null; 206 try { 207 // this will be set in the root site 208 cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport()); 209 exportData = OpenCms.getStaticExportManager().getExportData(req, cms); 210 } catch (CmsException e) { 211 // unlikely to happen 212 if (LOG.isWarnEnabled()) { 213 LOG.warn( 214 Messages.get().getBundle().key( 215 Messages.LOG_INIT_CMSOBJECT_IN_HANDLER_2, 216 name, 217 OpenCmsCore.getInstance().getPathInfo(req)), 218 e); 219 } 220 } 221 if (exportData != null) { 222 try { 223 // generate a static export request wrapper 224 CmsStaticExportRequest exportReq = new CmsStaticExportRequest(req, exportData); 225 // export the resource and set the response status according to the result 226 res.setStatus(OpenCms.getStaticExportManager().export(exportReq, res, cms, exportData)); 227 } catch (Throwable t) { 228 if (LOG.isWarnEnabled()) { 229 LOG.warn(Messages.get().getBundle().key(Messages.LOG_ERROR_EXPORT_1, exportData), t); 230 } 231 openErrorHandler(req, res, errorCode); 232 } 233 } else { 234 openErrorHandler(req, res, errorCode); 235 } 236 break; 237 default: 238 openErrorHandler(req, res, errorCode); 239 } 240 } 241 242 /** 243 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) 244 */ 245 @Override 246 public synchronized void init(ServletConfig config) throws ServletException { 247 248 super.init(config); 249 try { 250 // upgrade the runlevel 251 // usually this should have already been done by the context listener 252 // however, after a fresh install / setup this will be done from here 253 OpenCmsCore.getInstance().upgradeRunlevel(config.getServletContext()); 254 // finalize OpenCms initialization 255 OpenCmsCore.getInstance().initServlet(this); 256 } catch (CmsInitException e) { 257 if (Messages.ERR_CRITICAL_INIT_WIZARD_0.equals(e.getMessageContainer().getKey())) { 258 // if wizard is still enabled - allow retry of initialization (required for setup wizard) 259 // this means the servlet init() call must be terminated by an exception 260 if (CmsServletContainerSettings.isServletThrowsException()) { 261 throw new ServletException(e.getMessage()); 262 } else { 263 // this is needed since some servlet containers does not like the servlet to throw exceptions, 264 // like BEA WLS 9.x and Resin 265 LOG.error(Messages.get().getBundle().key(Messages.LOG_ERROR_GENERIC_0), e); 266 } 267 } 268 } catch (Throwable t) { 269 LOG.error(Messages.get().getBundle().key(Messages.LOG_ERROR_GENERIC_0), t); 270 } 271 } 272 273 /** 274 * Manages requests to internal OpenCms request handlers.<p> 275 * 276 * @param req the current request 277 * @param res the current response 278 * @throws ServletException in case an error occurs 279 * @throws ServletException in case an error occurs 280 * @throws IOException in case an error occurs 281 */ 282 protected void invokeHandler(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { 283 284 String name = OpenCmsCore.getInstance().getPathInfo(req).substring(HANDLE_PATH.length()); 285 I_CmsRequestHandler handler = OpenCmsCore.getInstance().getRequestHandler(name); 286 if ((handler == null) && name.contains("/")) { 287 // if the name contains a '/', also check for handlers matching the first path fragment only 288 name = name.substring(0, name.indexOf("/")); 289 handler = OpenCmsCore.getInstance().getRequestHandler(name); 290 } 291 if (handler != null) { 292 handler.handle(req, res, name); 293 } else { 294 openErrorHandler(req, res, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 295 } 296 } 297 298 /** 299 * Displays an error code handler loaded from the OpenCms VFS, 300 * or if such a page does not exist, 301 * displays the default servlet container error code.<p> 302 * 303 * @param req the current request 304 * @param res the current response 305 * @param errorCode the error code to display 306 * @throws IOException if something goes wrong 307 * @throws ServletException if something goes wrong 308 */ 309 protected void openErrorHandler(HttpServletRequest req, HttpServletResponse res, int errorCode) 310 throws IOException, ServletException { 311 312 String handlerUri = (new StringBuffer(64)).append(HANDLE_VFS_PATH).append(errorCode).append( 313 HANDLE_VFS_SUFFIX).toString(); 314 // provide the original error code in a request attribute 315 req.setAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE, new Integer(errorCode)); 316 CmsObject cms; 317 CmsFile file; 318 try { 319 // create OpenCms context, this will be set in the root site 320 cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 321 cms.getRequestContext().setSecureRequest(OpenCms.getSiteManager().usesSecureSite(req)); 322 } catch (CmsException e) { 323 // unlikely to happen as the OpenCms "Guest" context can always be initialized 324 CmsMessageContainer container = Messages.get().container( 325 Messages.LOG_INIT_CMSOBJECT_IN_HANDLER_2, 326 new Integer(errorCode), 327 handlerUri); 328 if (LOG.isWarnEnabled()) { 329 LOG.warn(org.opencms.jsp.Messages.getLocalizedMessage(container, req), e); 330 } 331 // however, if it _does_ happen, then we really can't continue here 332 if (!res.isCommitted()) { 333 // since the handler file is not accessible, display the default error page 334 res.sendError(errorCode, e.getLocalizedMessage()); 335 } 336 return; 337 } 338 try { 339 if (!tryCustomErrorPage(cms, req, res, errorCode)) { 340 cms.getRequestContext().setUri(handlerUri); 341 cms.getRequestContext().setSecureRequest(OpenCms.getSiteManager().usesSecureSite(req)); 342 // read the error handler file 343 file = cms.readFile(handlerUri, CmsResourceFilter.IGNORE_EXPIRATION); 344 OpenCms.getResourceManager().loadResource(cms, file, req, res); 345 } 346 } catch (CmsException e) { 347 // unable to load error page handler VFS resource 348 CmsMessageContainer container = Messages.get().container( 349 Messages.ERR_SHOW_ERR_HANDLER_RESOURCE_2, 350 new Integer(errorCode), 351 handlerUri); 352 throw new ServletException(org.opencms.jsp.Messages.getLocalizedMessage(container, req), e); 353 } 354 } 355 356 /** 357 * Tries to load the custom error page at the given rootPath. 358 * @param cms {@link CmsObject} used for reading the resource (site root and uri get adjusted!) 359 * @param req the current request 360 * @param res the current response 361 * @param rootPath the VFS root path to the error page resource 362 * @return a flag, indicating if the error page could be loaded 363 */ 364 private boolean loadCustomErrorPage( 365 CmsObject cms, 366 HttpServletRequest req, 367 HttpServletResponse res, 368 String rootPath) { 369 370 try { 371 372 // get the site of the error page resource 373 CmsSite errorSite = OpenCms.getSiteManager().getSiteForRootPath(rootPath); 374 cms.getRequestContext().setSiteRoot(errorSite.getSiteRoot()); 375 String relPath = cms.getRequestContext().removeSiteRoot(rootPath); 376 if (cms.existsResource(relPath)) { 377 cms.getRequestContext().setUri(relPath); 378 OpenCms.getResourceManager().loadResource(cms, cms.readResource(relPath), req, res); 379 return true; 380 } else { 381 return false; 382 } 383 } catch (Throwable e) { 384 // something went wrong log the exception and return false 385 LOG.error(e.getMessage(), e); 386 return false; 387 } 388 } 389 390 /** 391 * Tries to load a site specific error page. If 392 * @param cms {@link CmsObject} used for reading the resource (site root and uri get adjusted!) 393 * @param req the current request 394 * @param res the current response 395 * @param errorCode the error code to display 396 * @return a flag, indicating if the custom error page could be loaded. 397 */ 398 private boolean tryCustomErrorPage(CmsObject cms, HttpServletRequest req, HttpServletResponse res, int errorCode) { 399 400 String siteRoot = OpenCms.getSiteManager().matchRequest(req).getSiteRoot(); 401 CmsSite site = OpenCms.getSiteManager().getSiteForSiteRoot(siteRoot); 402 if (site != null) { 403 // store current site root and URI 404 String currentSiteRoot = cms.getRequestContext().getSiteRoot(); 405 String currentUri = cms.getRequestContext().getUri(); 406 try { 407 if (site.getErrorPage() != null) { 408 String rootPath = site.getErrorPage(); 409 if (loadCustomErrorPage(cms, req, res, rootPath)) { 410 return true; 411 } 412 } 413 String rootPath = CmsStringUtil.joinPaths(siteRoot, "/.errorpages/handle" + errorCode + ".html"); 414 if (loadCustomErrorPage(cms, req, res, rootPath)) { 415 return true; 416 } 417 } finally { 418 cms.getRequestContext().setSiteRoot(currentSiteRoot); 419 cms.getRequestContext().setUri(currentUri); 420 } 421 } 422 return false; 423 } 424}