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