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