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.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 doMigrationAndInit(pluginCM); 315 } 316 317 private void _initLogger() 318 { 319 // Configure Log4j 320 String logj4fFile = _servletContext.getRealPath("/WEB-INF/log4j.xml"); 321 322 // Hack to have context-relative log files, because of lack in configuration capabilities in log4j. 323 // If there are more than one Ametys in the same JVM, the property will be successively set for each instance, 324 // so we heavily rely on DOMConfigurator beeing synchronous. 325 System.setProperty("ametys.home.dir", _ametysHome.getAbsolutePath()); 326 DOMConfigurator.configure(logj4fFile); 327 System.clearProperty("ametys.home.dir"); 328 329 Appender appender = new MemoryAppender(); 330 appender.setName(org.ametys.plugins.core.ui.log.LogManager.MEMORY_APPENDER_NAME); 331 LogManager.getRootLogger().addAppender(appender); 332 Enumeration<org.apache.log4j.Logger> categories = LogManager.getCurrentLoggers(); 333 while (categories.hasMoreElements()) 334 { 335 org.apache.log4j.Logger logger = categories.nextElement(); 336 logger.addAppender(appender); 337 } 338 339 _loggerManager = new SLF4JLoggerManager(); 340 _logger = LoggerFactory.getLogger(getClass()); 341 } 342 343 private void _createCocoon() throws Exception 344 { 345 _exception = null; 346 347 _avalonContext.put(Constants.CONTEXT_CLASS_LOADER, getClass().getClassLoader()); 348 _avalonContext.put(Constants.CONTEXT_CLASSPATH, ""); 349 350 URL configFile = (URL) _avalonContext.get(Constants.CONTEXT_CONFIG_URL); 351 352 _logger.info("Reloading from: {}", configFile.toExternalForm()); 353 354 Cocoon c = (Cocoon) ClassUtils.newInstance("org.apache.cocoon.Cocoon"); 355 ContainerUtil.enableLogging(c, new SLF4JLoggerAdapter(_logger)); 356 c.setLoggerManager(_loggerManager); 357 ContainerUtil.contextualize(c, _avalonContext); 358 ContainerUtil.initialize(c); 359 360 _cocoon = c; 361 } 362 363 /** 364 * Init migration and plugins 365 * @param pluginCM Plugins Component Manager 366 * @throws Exception Something went wrong 367 */ 368 public static void doMigrationAndInit(PluginsComponentManager pluginCM) throws Exception 369 { 370 _initMigration(pluginCM); 371 _initPlugins(pluginCM); 372 } 373 374 private static void _initPlugins(PluginsComponentManager pluginCM) throws Exception 375 { 376 // If we're in safe mode 377// if (!PluginsManager.getInstance().isSafeMode()) 378// { 379 // FIXME the conditional was commented temporary for creating admin users SQL table even in safe mode 380 381 // Plugins Init class execution 382 InitExtensionPoint initExtensionPoint = (InitExtensionPoint) pluginCM.lookup(InitExtensionPoint.ROLE); 383 for (String id : initExtensionPoint.getExtensionsIds()) 384 { 385 Init init = initExtensionPoint.getExtension(id); 386 init.init(); 387 } 388 389 // Application Init class execution if available 390 if (pluginCM.hasComponent(Init.ROLE)) 391 { 392 Init init = (Init) pluginCM.lookup(Init.ROLE); 393 init.init(); 394 } 395 396// } 397 } 398 399 private static void _initMigration(PluginsComponentManager pluginCM) throws Exception 400 { 401 if (!PluginsManager.getInstance().isSafeMode()) 402 { 403 MigrationExtensionPoint migrationEP = (MigrationExtensionPoint) pluginCM.lookup(MigrationExtensionPoint.ROLE); 404 migrationEP.doMigration(); 405 } 406 } 407 408 private void _loadRuntimeConfig() throws ServletException 409 { 410 Configuration runtimeConf = null; 411 try 412 { 413 // XML Schema validation 414 SAXParserFactory factory = SAXParserFactory.newInstance(); 415 factory.setNamespaceAware(true); 416 417 URL schemaURL = getClass().getResource("runtime-4.1.xsd"); 418 Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaURL); 419 factory.setSchema(schema); 420 421 XMLReader reader = factory.newSAXParser().getXMLReader(); 422 DefaultConfigurationBuilder runtimeConfBuilder = new DefaultConfigurationBuilder(reader); 423 424 File runtimeConfigFile = new File(_servletContextPath, "WEB-INF/param/runtime.xml"); 425 try (InputStream runtime = new FileInputStream(runtimeConfigFile)) 426 { 427 runtimeConf = runtimeConfBuilder.build(runtime, runtimeConfigFile.getAbsolutePath()); 428 } 429 } 430 catch (Exception e) 431 { 432 _logger.error("Unable to load runtime file at 'WEB-INF/param/runtime.xml'. PluginsManager will enter in safe mode.", e); 433 } 434 435 Configuration externalConf = null; 436 try 437 { 438 DefaultConfigurationBuilder externalConfBuilder = new DefaultConfigurationBuilder(); 439 440 File externalConfigFile = new File(_servletContextPath, EXTERNAL_LOCATIONS); 441 if (externalConfigFile.exists()) 442 { 443 try (InputStream external = new FileInputStream(externalConfigFile)) 444 { 445 externalConf = externalConfBuilder.build(external, externalConfigFile.getAbsolutePath()); 446 } 447 } 448 } 449 catch (Exception e) 450 { 451 _logger.error("Unable to load external locations values at " + EXTERNAL_LOCATIONS, e); 452 throw new ServletException("Unable to load external locations values at " + EXTERNAL_LOCATIONS, e); 453 } 454 455 RuntimeConfig.configure(runtimeConf, externalConf, _ametysHome, _servletContextPath); 456 } 457 458 @Override 459 public void destroy() 460 { 461 _mode = RunMode.STOPPING; 462 463 if (_cocoon != null) 464 { 465 _logger.debug("Servlet destroyed - disposing Cocoon"); 466 _disposeCocoon(); 467 } 468 469 if (_ametysHomeLock != null) 470 { 471 _ametysHomeLock.release(); 472 _ametysHomeLock = null; 473 } 474 475 _avalonContext = null; 476 _logger = null; 477 _loggerManager = null; 478 } 479 480 481 private final void _disposeCocoon() 482 { 483 if (_cocoon != null) 484 { 485 ContainerUtil.dispose(_cocoon); 486 _cocoon = null; 487 } 488 } 489 490 @Override 491 public final void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException 492 { 493 req.setCharacterEncoding("UTF-8"); 494 495 // add the cocoon header timestamp 496 res.addHeader("X-Cocoon-Version", Constants.VERSION); 497 498 // Error mode 499 if (_exception != null) 500 { 501 _renderError(req, res, _exception, "An error occured during Ametys initialization."); 502 return; 503 } 504 505 String uri = _retrieveUri(req); 506 507 if (PluginsManager.getInstance().isSafeMode()) 508 { 509 // safe mode 510 String finalUri = uri; 511 boolean allowed = _allowedURLPattern.stream().anyMatch(p -> p.matcher(finalUri).matches()); 512 if (!allowed) 513 { 514 res.addHeader("X-Ametys-SafeMode", "true"); 515 516 Status status = PluginsManager.getInstance().getStatus(); 517 518 if (status == Status.NO_CONFIG) 519 { 520 res.sendRedirect(req.getContextPath() + "/_admin/public/first-start.html"); 521 return; 522 } 523 else if (status == Status.CONFIG_INCOMPLETE) 524 { 525 res.sendRedirect(req.getContextPath() + "/_admin/public/load-config.html"); 526 return; 527 } 528 else 529 { 530 res.sendRedirect(req.getContextPath() + "/_admin/public/safe-mode.html"); 531 return; 532 } 533 } 534 } 535 536 HttpSession session = req.getSession(false); 537 if (session != null) 538 { 539 UserIdentity authenticatedUser = (UserIdentity) session.getAttribute("Runtime:UserIdentity"); 540 541 if (authenticatedUser != null) 542 { 543 MDC.put("user", UserIdentity.userIdentityToString(authenticatedUser)); 544 } 545 } 546 547 MDC.put("requestURI", req.getRequestURI()); 548 549 // if (getRunMode() == RunMode.MAINTENANCE && !_accept(req)) 550 // { 551 // _runMaintenanceMode(req, res); 552 // } 553 // else 554 // { 555 556 StopWatch stopWatch = new StopWatch(); 557 HttpServletRequest request = null; 558 try 559 { 560 // used for timing the processing 561 stopWatch.start(); 562 563 _fireRequestStarted(req); 564 565 // get the request (wrapped if contains multipart-form data) 566 request = _requestFactory.getServletRequest(req); 567 568 // Process the request 569 HttpEnvironment env = new HttpEnvironment(uri, _servletContextURL.toExternalForm(), request, res, _servletContext, _context, "UTF-8", "UTF-8"); 570 env.enableLogging(new SLF4JLoggerAdapter(_logger)); 571 572 if (!_cocoon.process(env)) 573 { 574 // We reach this when there is nothing in the processing change that matches 575 // the request. For example, no matcher matches. 576 _logger.error("The Cocoon engine failed to process the request."); 577 _renderError(request, res, null, "Cocoon engine failed to process the request"); 578 } 579 } 580 catch (ResourceNotFoundException | ConnectionResetException | IOException e) 581 { 582 _logger.warn(e.toString()); 583 _renderError(request, res, e, e.getMessage()); 584 } 585 catch (Throwable e) 586 { 587 _logger.error("Internal Cocoon Problem", e); 588 _renderError(request, res, e, "Internal Cocoon Problem"); 589 } 590 finally 591 { 592 stopWatch.stop(); 593 _logger.info("'{}' processed in {} ms.", uri, stopWatch.getTime()); 594 595 try 596 { 597 if (request instanceof MultipartHttpServletRequest) 598 { 599 _logger.debug("Deleting uploaded file(s)."); 600 ((MultipartHttpServletRequest) request).cleanup(); 601 } 602 } 603 catch (IOException e) 604 { 605 _logger.error("Cocoon got an Exception while trying to cleanup the uploaded files.", e); 606 } 607 } 608 609 _fireRequestEnded(req); 610 611 if (Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload"))) 612 { 613 restartCocoon(req); 614 } 615 // } 616 } 617 618 /** 619 * Restart cocoon 620 * @param req The http servlet request, used to read safeMode and normalMode 621 * request attribute if possible. If null, safe mode will be 622 * forced only if it was alread forced. 623 */ 624 public void restartCocoon(HttpServletRequest req) 625 { 626 try 627 { 628 ConfigManager.getInstance().dispose(); 629 _disposeCocoon(); 630 _servletContext.removeAttribute("PluginsComponentManager"); 631 632 // By default force safe mode if it was already forced 633 boolean wasForcedSafeMode = PluginsManager.Status.SAFE_MODE_FORCED.equals(PluginsManager.getInstance().getStatus()); 634 boolean forceSafeMode = wasForcedSafeMode; 635 if (req != null) 636 { 637 // Also, checks if there is some specific request attributes 638 // Force safe mode if is explicitly requested 639 // Or force safe mode it was already forced unless normal mode is explicitly requested 640 forceSafeMode = Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload.safeMode")); 641 if (!forceSafeMode) 642 { 643 // 644 forceSafeMode = wasForcedSafeMode && !Boolean.TRUE.equals(req.getAttribute("org.ametys.runtime.reload.normalMode")); 645 } 646 } 647 648 _servletContext.setAttribute("org.ametys.runtime.forceSafeMode", forceSafeMode); 649 650 _initAmetys(); 651 } 652 catch (Exception e) 653 { 654 _logger.error("Error while reloading Ametys. Entering in error mode.", e); 655 _exception = e; 656 } 657 } 658 659 private String _retrieveUri(HttpServletRequest req) 660 { 661 String uri = req.getServletPath(); 662 663 String pathInfo = req.getPathInfo(); 664 if (pathInfo != null) 665 { 666 uri += pathInfo; 667 } 668 669 if (uri.length() > 0 && uri.charAt(0) == '/') 670 { 671 uri = uri.substring(1); 672 } 673 return uri; 674 } 675 676 @SuppressWarnings("unchecked") 677 private void _fireRequestStarted(HttpServletRequest req) 678 { 679 Collection< ? extends RequestListener> listeners = (Collection< ? extends RequestListener>) _servletContext.getAttribute(RequestListenerManager.CONTEXT_ATTRIBUTE_REQUEST_LISTENERS); 680 681 if (listeners == null) 682 { 683 return; 684 } 685 686 for (RequestListener listener : listeners) 687 { 688 listener.requestStarted(req); 689 } 690 } 691 692 @SuppressWarnings("unchecked") 693 private void _fireRequestEnded(HttpServletRequest req) 694 { 695 Collection<? extends RequestListener> listeners = (Collection< ? extends RequestListener>) _servletContext.getAttribute(RequestListenerManager.CONTEXT_ATTRIBUTE_REQUEST_LISTENERS); 696 697 if (listeners == null) 698 { 699 return; 700 } 701 702 for (RequestListener listener : listeners) 703 { 704 listener.requestEnded(req); 705 } 706 } 707 708 /** 709 * Set the run mode 710 * @param mode the running mode 711 */ 712 public static void setRunMode(RunMode mode) 713 { 714 _mode = mode; 715 } 716 717 /** 718 * Get the run mode 719 * @return the current run mode 720 */ 721 public static RunMode getRunMode() 722 { 723 return _mode; 724 } 725 726 private void _renderError(HttpServletRequest req, HttpServletResponse res, Throwable throwable, String message) throws ServletException 727 { 728 ServletConfig config = getServletConfig(); 729 730 if (config == null) 731 { 732 throw new ServletException("Cannot access to ServletConfig"); 733 } 734 735 try 736 { 737 ServletOutputStream os = res.getOutputStream(); 738 String path = req.getRequestURI().substring(req.getContextPath().length()); 739 740 // Favicon associated with the error page. 741 if (path.equals("/favicon.ico")) 742 { 743 try (InputStream is = getClass().getResourceAsStream("favicon.ico")) 744 { 745 res.setStatus(200); 746 res.setContentType(config.getServletContext().getMimeType("favicon.ico")); 747 748 IOUtils.copy(is, os); 749 750 return; 751 } 752 } 753 754 res.setStatus(500); 755 res.setContentType("text/html; charset=UTF-8"); 756 757 SAXTransformerFactory saxFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 758 TransformerHandler th; 759 760 try (InputStream is = getClass().getResourceAsStream("fatal.xsl")) 761 { 762 StreamSource errorSource = new StreamSource(is); 763 th = saxFactory.newTransformerHandler(errorSource); 764 } 765 766 Properties format = new Properties(); 767 format.put(OutputKeys.METHOD, "xml"); 768 format.put(OutputKeys.ENCODING, "UTF-8"); 769 format.put(OutputKeys.DOCTYPE_SYSTEM, "about:legacy-compat"); 770 771 th.getTransformer().setOutputProperties(format); 772 773 th.getTransformer().setParameter("code", 500); 774 th.getTransformer().setParameter("realPath", config.getServletContext().getRealPath("/")); 775 th.getTransformer().setParameter("contextPath", req.getContextPath()); 776 777 StreamResult result = new StreamResult(os); 778 th.setResult(result); 779 780 th.startDocument(); 781 782 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "exception-report"); 783 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "message"); 784 XMLUtils.data(th, message); 785 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "message"); 786 787 XMLUtils.startElement(th, "http://apache.org/cocoon/exception/1.0", "stacktrace"); 788 XMLUtils.data(th, ExceptionUtils.getStackTrace(throwable)); 789 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "stacktrace"); 790 XMLUtils.endElement(th, "http://apache.org/cocoon/exception/1.0", "ex:exception-report"); 791 792 th.endDocument(); 793 } 794 catch (Exception e) 795 { 796 // Nothing we can do anymore ... 797 throw new ServletException(e); 798 } 799 } 800 801 /* 802 * 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); } } 803 */ 804 805 /** 806 * In maintenance mode, send error information as SAX events.<br> 807 * 808 * @param ch the contentHandler receiving the message 809 * @throws SAXException if an error occured while send SAX events 810 */ 811 /* 812 * 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"); } 813 */ 814}