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