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