001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.servlet; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.URL; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Enumeration; 026import java.util.Properties; 027import java.util.regex.Pattern; 028 029import javax.servlet.ServletConfig; 030import javax.servlet.ServletContext; 031import javax.servlet.ServletException; 032import javax.servlet.ServletOutputStream; 033import javax.servlet.http.HttpServlet; 034import javax.servlet.http.HttpServletRequest; 035import javax.servlet.http.HttpServletResponse; 036import javax.servlet.http.HttpSession; 037import javax.xml.XMLConstants; 038import javax.xml.parsers.SAXParserFactory; 039import javax.xml.transform.OutputKeys; 040import javax.xml.transform.TransformerFactory; 041import javax.xml.transform.sax.SAXTransformerFactory; 042import javax.xml.transform.sax.TransformerHandler; 043import javax.xml.transform.stream.StreamResult; 044import javax.xml.transform.stream.StreamSource; 045import javax.xml.validation.Schema; 046import javax.xml.validation.SchemaFactory; 047 048import org.apache.avalon.excalibur.logger.LoggerManager; 049import org.apache.avalon.framework.configuration.Configuration; 050import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 051import org.apache.avalon.framework.container.ContainerUtil; 052import org.apache.avalon.framework.context.DefaultContext; 053import org.apache.cocoon.Cocoon; 054import org.apache.cocoon.ConnectionResetException; 055import org.apache.cocoon.Constants; 056import org.apache.cocoon.ResourceNotFoundException; 057import org.apache.cocoon.environment.http.HttpContext; 058import org.apache.cocoon.environment.http.HttpEnvironment; 059import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest; 060import org.apache.cocoon.servlet.multipart.RequestFactory; 061import org.apache.cocoon.util.ClassUtils; 062import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 063import org.apache.cocoon.util.log.SLF4JLoggerManager; 064import org.apache.cocoon.xml.XMLUtils; 065import org.apache.commons.collections.EnumerationUtils; 066import org.apache.commons.io.FileUtils; 067import org.apache.commons.io.IOUtils; 068import org.apache.commons.lang3.exception.ExceptionUtils; 069import org.apache.commons.lang3.time.StopWatch; 070import org.apache.commons.lang3.StringUtils; 071import org.apache.log4j.Appender; 072import org.apache.log4j.LogManager; 073import org.apache.log4j.xml.DOMConfigurator; 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076import org.slf4j.MDC; 077import org.xml.sax.XMLReader; 078 079import org.ametys.core.migration.MigrationExtensionPoint; 080import org.ametys.core.user.UserIdentity; 081import org.ametys.runtime.config.Config; 082import org.ametys.runtime.config.ConfigManager; 083import org.ametys.runtime.data.AmetysHomeLock; 084import org.ametys.runtime.data.AmetysHomeLockException; 085import org.ametys.runtime.log.MemoryAppender; 086import org.ametys.runtime.plugin.Init; 087import org.ametys.runtime.plugin.InitExtensionPoint; 088import org.ametys.runtime.plugin.PluginsManager; 089import org.ametys.runtime.plugin.PluginsManager.Status; 090import org.ametys.runtime.plugin.component.PluginsComponentManager; 091import org.ametys.runtime.request.RequestListener; 092import org.ametys.runtime.request.RequestListenerManager; 093import org.ametys.runtime.util.AmetysHomeHelper; 094 095/** 096 * Main entry point for applications.<br> 097 * Overrides the CocoonServlet to add some initialization.<br> 098 */ 099public class RuntimeServlet extends HttpServlet 100{ 101 /** Constant for storing the {@link ServletConfig} in the Avalon context */ 102 public static final String CONTEXT_SERVLET_CONFIG = "servlet-config"; 103 104 /** Constant for storing the servlet context URL in the Avalon context */ 105 public static final String CONTEXT_CONTEXT_ROOT = "context-root"; 106 107 /** The cocoon.xconf URL */ 108 public static final String COCOON_CONF_URL = "/org/ametys/runtime/cocoon/cocoon.xconf"; 109 110 /** Default max upload size (10 Mb) */ 111 public static final int DEFAULT_MAX_UPLOAD_SIZE = 10 * 1024 * 1024; 112 113 /** The config file name */ 114 public static final String CONFIG_FILE_NAME = "config.xml"; 115 116 /** Name of the servlet initialization parameter for the ametys home property */ 117 public static final String AMETYS_HOME_PROPERTY = "ametys.home.property"; 118 119 /** The default ametys home path (relative to the servlet context) */ 120 public static final String AMETYS_HOME_DEFAULT = "/WEB-INF/data"; 121 122 /** The optional location (relative to application context) of the file that configures the list of plugins/workspaces/kernel to get somewhere else */ 123 public static final String EXTERNAL_LOCATIONS = "WEB-INF/param/external-locations.xml"; 124 125 /** The run modes */ 126 public enum RunMode 127 { 128 /** Normal execution mode */ 129 NORMAL, 130 /** Maintenance mode required by administrator */ 131 // MAINTENANCE, 132 /** Application is currently stopping */ 133 STOPPING 134 } 135 136 private static RunMode _mode = RunMode.NORMAL; 137 138 private ServletContext _servletContext; 139 private String _servletContextPath; 140 private URL _servletContextURL; 141 142 private DefaultContext _avalonContext; 143 private HttpContext _context; 144 private Cocoon _cocoon; 145 private RequestFactory _requestFactory; 146 private File _workDir; 147 148 private int _maxUploadSize = DEFAULT_MAX_UPLOAD_SIZE; 149 private File _uploadDir; 150 private File _cacheDir; 151 152 private File _ametysHome; 153 private AmetysHomeLock _ametysHomeLock; 154 155 private Logger _logger; 156 private LoggerManager _loggerManager; 157 158 private Exception _exception; 159 160 private Collection<Pattern> _allowedURLPattern = Arrays.asList(Pattern.compile("_admin($|/.*)"), Pattern.compile("plugins/[^/]+/resources/.*")); 161 162 @Override 163 public void init() throws ServletException 164 { 165 try 166 { 167 // Set this property in order to avoid a System.err.println (CatalogManager.java) 168 if (System.getProperty("xml.catalog.ignoreMissing") == null) 169 { 170 System.setProperty("xml.catalog.ignoreMissing", "true"); 171 } 172 173 _servletContext = getServletContext(); 174 _servletContextPath = _servletContext.getRealPath("/"); 175 176 _avalonContext = new DefaultContext(); 177 _context = new HttpContext(_servletContext); 178 _avalonContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, _context); 179 _avalonContext.put(Constants.CONTEXT_DEFAULT_ENCODING, "UTF-8"); 180 _avalonContext.put(CONTEXT_SERVLET_CONFIG, getServletConfig()); 181 182 _servletContextURL = new File(_servletContextPath).toURI().toURL(); 183 _avalonContext.put(CONTEXT_CONTEXT_ROOT, _servletContextURL); 184 185 URL configFile = getClass().getResource(COCOON_CONF_URL); 186 _avalonContext.put(Constants.CONTEXT_CONFIG_URL, configFile); 187 188 _workDir = new File((File) _servletContext.getAttribute("javax.servlet.context.tempdir"), "cocoon-files"); 189 _workDir.mkdirs(); 190 _avalonContext.put(Constants.CONTEXT_WORK_DIR, _workDir); 191 192 _cacheDir = new File(_workDir, "cache-dir"); 193 _cacheDir.mkdirs(); 194 _avalonContext.put(Constants.CONTEXT_CACHE_DIR, _cacheDir); 195 196 // Create temp dir if it does not exist 197 File tmpDir = new File(System.getProperty("java.io.tmpdir")); 198 if (!tmpDir.exists()) 199 { 200 FileUtils.forceMkdir(tmpDir); 201 } 202 203 // Init the Ametys home directory and lock before setting the logger. 204 // The log folder is located in the Ametys home directory. 205 _initAmetysHome(); 206 207 // Configuration file 208 File config = FileUtils.getFile(_ametysHome, AmetysHomeHelper.AMETYS_HOME_CONFIG_DIR, CONFIG_FILE_NAME); 209 final String fileName = config.getCanonicalPath(); 210 Config.setFilename(fileName); 211 212 // Init logger 213 _initLogger(); 214 215 _initAmetys(); 216 } 217 catch (Throwable t) 218 { 219 if (_logger != null) 220 { 221 _logger.error("Error while loading Ametys. Entering in error mode.", t); 222 } 223 else 224 { 225 System.out.println("Error while loading Ametys. Entering in error mode."); 226 t.printStackTrace(); 227 } 228 229 if (t instanceof Exception) 230 { 231 _exception = (Exception) t; 232 } 233 else 234 { 235 _exception = new Exception(t); 236 } 237 238 _disposeCocoon(); 239 } 240 } 241 242 private void _initAmetysHome() throws AmetysHomeLockException 243 { 244 String ametysHomePath = null; 245 246 boolean hasAmetysHomeProp = EnumerationUtils.toList(getInitParameterNames()).contains(AMETYS_HOME_PROPERTY); 247 if (!hasAmetysHomeProp) 248 { 249 System.out.println(String.format("[INFO] The '%s' servlet initialization parameter was not found. The Ametys home directory default value will be used '%s'", 250 AMETYS_HOME_PROPERTY, AMETYS_HOME_DEFAULT)); 251 } 252 else 253 { 254 String ametysHomeEnv = getInitParameter(AMETYS_HOME_PROPERTY); 255 if (StringUtils.isEmpty(ametysHomeEnv)) 256 { 257 System.out.println(String.format("[WARN] The '%s' servlet initialization parameter appears to be empty. Ametys home directory default value will be used '%s'", 258 AMETYS_HOME_PROPERTY, AMETYS_HOME_DEFAULT)); 259 } 260 else 261 { 262 ametysHomePath = System.getenv(ametysHomeEnv); 263 if (StringUtils.isEmpty(ametysHomePath)) 264 { 265 System.out.println(String.format( 266 "[WARN] The '%s' environment variable was not found or was empty. Ametys home directory default value will be used '%s'", 267 ametysHomeEnv, AMETYS_HOME_DEFAULT)); 268 } 269 } 270 } 271 272 if (StringUtils.isEmpty(ametysHomePath)) 273 { 274 ametysHomePath = _servletContext.getRealPath(AMETYS_HOME_DEFAULT); 275 } 276 277 System.out.println("Acquiring lock on " + ametysHomePath); 278 279 // Acquire the lock on Ametys home 280 _ametysHome = new File(ametysHomePath); 281 _ametysHome.mkdirs(); 282 283 _ametysHomeLock = new AmetysHomeLock(_ametysHome); 284 _ametysHomeLock.acquire(); 285 } 286 287 private void _initAmetys() throws Exception 288 { 289 // WEB-INF/param/runtime.xml loading 290 _loadRuntimeConfig(); 291 292 _createCocoon(); 293 294 // Upload initialization 295 _maxUploadSize = DEFAULT_MAX_UPLOAD_SIZE; 296 _uploadDir = new File(RuntimeConfig.getInstance().getAmetysHome(), "uploads"); 297 298 if (ConfigManager.getInstance().isComplete()) 299 { 300 Long maxUploadSizeParam = Config.getInstance().getValue("runtime.upload.max-size"); 301 if (maxUploadSizeParam != null) 302 { 303 // if the feature core/runtime.upload is deactivated, use the default value (10 Mb) 304 _maxUploadSize = maxUploadSizeParam.intValue(); 305 } 306 } 307 308 _uploadDir.mkdirs(); 309 _avalonContext.put(Constants.CONTEXT_UPLOAD_DIR, _uploadDir); 310 311 _requestFactory = new RequestFactory(true, _uploadDir, false, true, _maxUploadSize, "UTF-8"); 312 313 PluginsComponentManager pluginCM = (PluginsComponentManager) _servletContext.getAttribute("PluginsComponentManager"); 314 if (doMigrationAndInit(pluginCM)) 315 { 316 restartCocoon(null); 317 } 318 } 319 320 private void _initLogger() 321 { 322 // Configure Log4j 323 String logj4fFile = _servletContext.getRealPath("/WEB-INF/log4j.xml"); 324 325 // Hack to have context-relative log files, because of lack in configuration capabilities in log4j. 326 // If there are more than one Ametys in the same JVM, the property will be successively set for each instance, 327 // so we heavily rely on DOMConfigurator beeing synchronous. 328 System.setProperty("ametys.home.dir", _ametysHome.getAbsolutePath()); 329 DOMConfigurator.configure(logj4fFile); 330 System.clearProperty("ametys.home.dir"); 331 332 Appender appender = new MemoryAppender(); 333 appender.setName(org.ametys.plugins.core.ui.log.LogManager.MEMORY_APPENDER_NAME); 334 LogManager.getRootLogger().addAppender(appender); 335 Enumeration<org.apache.log4j.Logger> categories = LogManager.getCurrentLoggers(); 336 while (categories.hasMoreElements()) 337 { 338 org.apache.log4j.Logger logger = categories.nextElement(); 339 logger.addAppender(appender); 340 } 341 342 _loggerManager = new SLF4JLoggerManager(); 343 _logger = LoggerFactory.getLogger(getClass()); 344 } 345 346 private void _createCocoon() throws Exception 347 { 348 _exception = null; 349 350 _avalonContext.put(Constants.CONTEXT_CLASS_LOADER, getClass().getClassLoader()); 351 _avalonContext.put(Constants.CONTEXT_CLASSPATH, ""); 352 353 URL configFile = (URL) _avalonContext.get(Constants.CONTEXT_CONFIG_URL); 354 355 _logger.info("Reloading from: {}", configFile.toExternalForm()); 356 357 Cocoon c = (Cocoon) ClassUtils.newInstance("org.apache.cocoon.Cocoon"); 358 ContainerUtil.enableLogging(c, new SLF4JLoggerAdapter(_logger)); 359 c.setLoggerManager(_loggerManager); 360 ContainerUtil.contextualize(c, _avalonContext); 361 ContainerUtil.initialize(c); 362 363 _cocoon = c; 364 } 365 366 /** 367 * Init migration and plugins 368 * @param pluginCM Plugins Component Manager 369 * @return true if a server restart is required 370 * @throws Exception Something went wrong 371 */ 372 public static boolean doMigrationAndInit(PluginsComponentManager pluginCM) throws Exception 373 { 374 if (_initMigration(pluginCM)) 375 { 376 return true; 377 } 378 379 _initPlugins(pluginCM); 380 return false; 381 } 382 383 private static void _initPlugins(PluginsComponentManager pluginCM) throws Exception 384 { 385 // If we're in safe mode 386// if (!PluginsManager.getInstance().isSafeMode()) 387// { 388 // FIXME the conditional was commented temporary for creating admin users SQL table even in safe mode 389 390 // Plugins Init class execution 391 InitExtensionPoint initExtensionPoint = (InitExtensionPoint) pluginCM.lookup(InitExtensionPoint.ROLE); 392 for (String id : initExtensionPoint.getExtensionsIds()) 393 { 394 Init init = initExtensionPoint.getExtension(id); 395 init.init(); 396 } 397 398 // Application Init class execution if available 399 if (pluginCM.hasComponent(Init.ROLE)) 400 { 401 Init init = (Init) pluginCM.lookup(Init.ROLE); 402 init.init(); 403 } 404 405// } 406 } 407 408 private static boolean _initMigration(PluginsComponentManager pluginCM) throws Exception 409 { 410 MigrationExtensionPoint migrationEP = (MigrationExtensionPoint) pluginCM.lookup(MigrationExtensionPoint.ROLE); 411 return migrationEP.doMigration(); 412 } 413 414 private void _loadRuntimeConfig() throws ServletException 415 { 416 Configuration runtimeConf = null; 417 try 418 { 419 // XML Schema validation 420 SAXParserFactory factory = SAXParserFactory.newInstance(); 421 factory.setNamespaceAware(true); 422 423 URL schemaURL = getClass().getResource("runtime-4.1.xsd"); 424 Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaURL); 425 factory.setSchema(schema); 426 427 XMLReader reader = factory.newSAXParser().getXMLReader(); 428 DefaultConfigurationBuilder runtimeConfBuilder = new DefaultConfigurationBuilder(reader); 429 430 File runtimeConfigFile = new File(_servletContextPath, "WEB-INF/param/runtime.xml"); 431 try (InputStream runtime = new FileInputStream(runtimeConfigFile)) 432 { 433 runtimeConf = runtimeConfBuilder.build(runtime, runtimeConfigFile.getAbsolutePath()); 434 } 435 } 436 catch (Exception e) 437 { 438 _logger.error("Unable to load runtime file at 'WEB-INF/param/runtime.xml'. PluginsManager will enter in safe mode.", e); 439 } 440 441 Configuration externalConf = null; 442 try 443 { 444 DefaultConfigurationBuilder externalConfBuilder = new DefaultConfigurationBuilder(); 445 446 File externalConfigFile = new File(_servletContextPath, EXTERNAL_LOCATIONS); 447 if (externalConfigFile.exists()) 448 { 449 try (InputStream external = new FileInputStream(externalConfigFile)) 450 { 451 externalConf = externalConfBuilder.build(external, externalConfigFile.getAbsolutePath()); 452 } 453 } 454 } 455 catch (Exception e) 456 { 457 _logger.error("Unable to load external locations values at " + EXTERNAL_LOCATIONS, e); 458 throw new ServletException("Unable to load external locations values at " + EXTERNAL_LOCATIONS, e); 459 } 460 461 RuntimeConfig.configure(runtimeConf, externalConf, _ametysHome, _servletContextPath); 462 } 463 464 @Override 465 public void destroy() 466 { 467 _mode = RunMode.STOPPING; 468 469 if (_cocoon != null) 470 { 471 _logger.debug("Servlet destroyed - disposing Cocoon"); 472 _disposeCocoon(); 473 } 474 475 if (_ametysHomeLock != null) 476 { 477 _ametysHomeLock.release(); 478 _ametysHomeLock = null; 479 } 480 481 _avalonContext = null; 482 _logger = null; 483 _loggerManager = null; 484 } 485 486 487 private final void _disposeCocoon() 488 { 489 if (_cocoon != null) 490 { 491 ContainerUtil.dispose(_cocoon); 492 _cocoon = null; 493 } 494 } 495 496 @Override 497 public final void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException 498 { 499 req.setCharacterEncoding("UTF-8"); 500 501 // add the cocoon header timestamp 502 res.addHeader("X-Cocoon-Version", Constants.VERSION); 503 504 // Error mode 505 if (_exception != null) 506 { 507 _renderError(req, res, _exception, "An error occured during Ametys initialization."); 508 return; 509 } 510 511 String uri = _retrieveUri(req); 512 513 if (PluginsManager.getInstance().isSafeMode()) 514 { 515 // safe mode 516 String finalUri = uri; 517 boolean allowed = _allowedURLPattern.stream().anyMatch(p -> p.matcher(finalUri).matches()); 518 if (!allowed) 519 { 520 res.addHeader("X-Ametys-SafeMode", "true"); 521 522 Status status = PluginsManager.getInstance().getStatus(); 523 524 if (status == Status.NO_CONFIG) 525 { 526 res.sendRedirect(req.getContextPath() + "/_admin/public/first-start.html"); 527 return; 528 } 529 else if (status == Status.CONFIG_INCOMPLETE) 530 { 531 res.sendRedirect(req.getContextPath() + "/_admin/public/load-config.html"); 532 return; 533 } 534 else 535 { 536 res.sendRedirect(req.getContextPath() + "/_admin/public/safe-mode.html"); 537 return; 538 } 539 } 540 } 541 542 MDC.remove("user"); 543 544 HttpSession session = req.getSession(false); 545 if (session != null) 546 { 547 UserIdentity authenticatedUser = (UserIdentity) session.getAttribute("Runtime:UserIdentity"); 548 549 if (authenticatedUser != null) 550 { 551 MDC.put("user", UserIdentity.userIdentityToString(authenticatedUser)); 552 } 553 } 554 555 MDC.put("requestURI", req.getRequestURI()); 556 557 // if (getRunMode() == RunMode.MAINTENANCE && !_accept(req)) 558 // { 559 // _runMaintenanceMode(req, res); 560 // } 561 // else 562 // { 563 564 StopWatch stopWatch = new StopWatch(); 565 HttpServletRequest request = null; 566 try 567 { 568 // used for timing the processing 569 stopWatch.start(); 570 571 _fireRequestStarted(req); 572 573 // get the request (wrapped if contains multipart-form data) 574 request = _requestFactory.getServletRequest(req); 575 576 // Process the request 577 HttpEnvironment env = new HttpEnvironment(uri, _servletContextURL.toExternalForm(), request, res, _servletContext, _context, "UTF-8", "UTF-8"); 578 env.enableLogging(new SLF4JLoggerAdapter(_logger)); 579 580 if (!_cocoon.process(env)) 581 { 582 // We reach this when there is nothing in the processing change that matches 583 // the request. For example, no matcher matches. 584 _logger.error("The Cocoon engine failed to process the request."); 585 _renderError(request, res, null, "Cocoon engine failed to process the request"); 586 } 587 } 588 catch (ResourceNotFoundException | ConnectionResetException | IOException e) 589 { 590 _logger.warn(e.toString()); 591 _renderError(request, res, e, e.getMessage()); 592 } 593 catch (Throwable e) 594 { 595 _logger.error("Internal Cocoon Problem", e); 596 _renderError(request, res, e, "Internal Cocoon Problem"); 597 } 598 finally 599 { 600 stopWatch.stop(); 601 _logger.info("'{}' processed in {} ms.", uri, stopWatch.getTime()); 602 603 try 604 { 605 if (request instanceof MultipartHttpServletRequest) 606 { 607 _logger.debug("Deleting uploaded file(s)."); 608 ((MultipartHttpServletRequest) request).cleanup(); 609 } 610 } 611 catch (IOException e) 612 { 613 _logger.error("Cocoon got an Exception while trying to cleanup the uploaded files.", e); 614 } 615 } 616 617 _fireRequestEnded(req); 618 619 if (Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload"))) 620 { 621 restartCocoon(req); 622 } 623 // } 624 } 625 626 /** 627 * Restart cocoon 628 * @param req The http servlet request, used to read safeMode and normalMode 629 * request attribute if possible. If null, safe mode will be 630 * forced only if it was alread forced. 631 */ 632 public void restartCocoon(HttpServletRequest req) 633 { 634 try 635 { 636 ConfigManager.getInstance().dispose(); 637 _disposeCocoon(); 638 _servletContext.removeAttribute("PluginsComponentManager"); 639 640 // By default force safe mode if it was already forced 641 boolean wasForcedSafeMode = PluginsManager.Status.SAFE_MODE_FORCED.equals(PluginsManager.getInstance().getStatus()); 642 boolean forceSafeMode = wasForcedSafeMode; 643 if (req != null) 644 { 645 // Also, checks if there is some specific request attributes 646 // Force safe mode if is explicitly requested 647 // Or force safe mode it was already forced unless normal mode is explicitly requested 648 forceSafeMode = Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload.safeMode")); 649 if (!forceSafeMode) 650 { 651 // 652 forceSafeMode = wasForcedSafeMode && !Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload.normalMode")); 653 } 654 } 655 656 _servletContext.setAttribute("org.ametys.runtime.forceSafeMode", forceSafeMode); 657 658 _initAmetys(); 659 } 660 catch (Exception e) 661 { 662 _logger.error("Error while reloading Ametys. Entering in error mode.", e); 663 _exception = e; 664 } 665 } 666 667 private String _retrieveUri(HttpServletRequest req) 668 { 669 String uri = req.getServletPath(); 670 671 String pathInfo = req.getPathInfo(); 672 if (pathInfo != null) 673 { 674 uri += pathInfo; 675 } 676 677 if (uri.length() > 0 && uri.charAt(0) == '/') 678 { 679 uri = uri.substring(1); 680 } 681 return uri; 682 } 683 684 @SuppressWarnings("unchecked") 685 private void _fireRequestStarted(HttpServletRequest req) 686 { 687 Collection< ? extends RequestListener> listeners = (Collection< ? extends RequestListener>) _servletContext.getAttribute(RequestListenerManager.CONTEXT_ATTRIBUTE_REQUEST_LISTENERS); 688 689 if (listeners == null) 690 { 691 return; 692 } 693 694 for (RequestListener listener : listeners) 695 { 696 listener.requestStarted(req); 697 } 698 } 699 700 @SuppressWarnings("unchecked") 701 private void _fireRequestEnded(HttpServletRequest req) 702 { 703 Collection<? extends RequestListener> listeners = (Collection< ? extends RequestListener>) _servletContext.getAttribute(RequestListenerManager.CONTEXT_ATTRIBUTE_REQUEST_LISTENERS); 704 705 if (listeners == null) 706 { 707 return; 708 } 709 710 for (RequestListener listener : listeners) 711 { 712 listener.requestEnded(req); 713 } 714 } 715 716 /** 717 * Set the run mode 718 * @param mode the running mode 719 */ 720 public static void setRunMode(RunMode mode) 721 { 722 _mode = mode; 723 } 724 725 /** 726 * Get the run mode 727 * @return the current run mode 728 */ 729 public static RunMode getRunMode() 730 { 731 return _mode; 732 } 733 734 private void _renderError(HttpServletRequest req, HttpServletResponse res, Throwable throwable, String message) throws ServletException 735 { 736 ServletConfig config = getServletConfig(); 737 738 if (config == null) 739 { 740 throw new ServletException("Cannot access to ServletConfig"); 741 } 742 743 try 744 { 745 ServletOutputStream os = res.getOutputStream(); 746 String path = req.getRequestURI().substring(req.getContextPath().length()); 747 748 // Favicon associated with the error page. 749 if (path.equals("/favicon.ico")) 750 { 751 try (InputStream is = getClass().getResourceAsStream("favicon.ico")) 752 { 753 res.setStatus(200); 754 res.setContentType(config.getServletContext().getMimeType("favicon.ico")); 755 756 IOUtils.copy(is, os); 757 758 return; 759 } 760 } 761 762 res.setStatus(500); 763 res.setContentType("text/html; charset=UTF-8"); 764 765 SAXTransformerFactory saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 766 TransformerHandler th; 767 768 try (InputStream is = getClass().getResourceAsStream("fatal.xsl")) 769 { 770 StreamSource errorSource = new StreamSource(is); 771 th = saxFactory.newTransformerHandler(errorSource); 772 } 773 774 Properties format = new Properties(); 775 format.put(OutputKeys.METHOD, "xml"); 776 format.put(OutputKeys.ENCODING, "UTF-8"); 777 format.put(OutputKeys.DOCTYPE_SYSTEM, "about:legacy-compat"); 778 779 th.getTransformer().setOutputProperties(format); 780 781 th.getTransformer().setParameter("code", 500); 782 th.getTransformer().setParameter("realPath", config.getServletContext().getRealPath("/")); 783 th.getTransformer().setParameter("contextPath", req.getContextPath()); 784 785 StreamResult result = new StreamResult(os); 786 th.setResult(result); 787 788 th.startDocument(); 789 790 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "exception-report"); 791 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "message"); 792 XMLUtils.data(th, message); 793 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "message"); 794 795 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "stacktrace"); 796 XMLUtils.data(th, ExceptionUtils.getStackTrace(throwable)); 797 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "stacktrace"); 798 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "ex:exception-report"); 799 800 th.endDocument(); 801 } 802 catch (Exception e) 803 { 804 // Nothing we can do anymore ... 805 throw new ServletException(e); 806 } 807 } 808 809 /* 810 * private void _runMaintenanceMode(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletConfig config = getServletConfig(); if (config == null) { throw new ServletException("Cannot access to ServletConfig"); } ServletOutputStream os = res.getOutputStream(); if (req.getRequestURI().startsWith("/WEB-INF/error/")) { // Service des fichiers statiques de la page d'erreur File f = new File(config.getServletContext().getRealPath(req.getRequestURI())); if (f.exists()) { res.setStatus(200); InputStream is = new FileInputStream(f); SourceUtil.copy(is, os); is.close(); } else { res.setStatus(404); } return; } res.setStatus(500); SAXTransformerFactory saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); InputStream is; File errorXSL = new File(config.getServletContext().getRealPath("/WEB-INF/error/error.xsl")); if (errorXSL.exists()) { is = new FileInputStream(errorXSL); } else { is = getClass().getResourceAsStream("/org/ametys/runtime/kernel/pages/error/error.xsl"); } try { StreamSource errorSource = new StreamSource(is); Templates templates = saxFactory.newTemplates(errorSource); TransformerHandler th = saxFactory.newTransformerHandler(templates); is.close(); StreamResult result = new StreamResult(os); th.setResult(result); th.startDocument(); th.startElement("", "error", "error", new AttributesImpl()); saxMaintenanceMessage(th); th.endElement("", "error", "error"); th.endDocument(); } catch (Exception ex) { throw new ServletException("Unable to send maintenance page", ex); } } 811 */ 812 813 /** 814 * In maintenance mode, send error information as SAX events.<br> 815 * 816 * @param ch the contentHandler receiving the message 817 * @throws SAXException if an error occured while send SAX events 818 */ 819 /* 820 * protected void saxMaintenanceMessage(ContentHandler ch) throws SAXException { String maintenanceMessage = "The application is under maintenance. Please retry later."; ch.characters(maintenanceMessage.toCharArray(), 0, maintenanceMessage.length()); } private boolean _accept(HttpServletRequest req) { // FIX ME à ne pas mettre en dur return req.getRequestURI().startsWith("_admin"); } 821 */ 822}