• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 13965803842

20 Mar 2025 09:16AM UTC coverage: 65.073% (-0.008%) from 65.081%
13965803842

push

github

web-flow
TRUNK-6309: Fix logging issue at Listener.java (#4967)

0 of 1 new or added line in 1 file covered. (0.0%)

7 existing lines in 5 files now uncovered.

23404 of 35966 relevant lines covered (65.07%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.57
/web/src/main/java/org/openmrs/web/Listener.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.web;
11

12
import org.apache.logging.log4j.Level;
13
import org.apache.logging.log4j.LogManager;
14
import org.apache.logging.log4j.core.config.Configurator;
15
import org.openmrs.api.context.Context;
16
import org.openmrs.logging.OpenmrsLoggingUtil;
17
import org.openmrs.module.MandatoryModuleException;
18
import org.openmrs.module.Module;
19
import org.openmrs.module.ModuleFactory;
20
import org.openmrs.module.ModuleMustStartException;
21
import org.openmrs.module.OpenmrsCoreModuleException;
22
import org.openmrs.module.web.OpenmrsJspServlet;
23
import org.openmrs.module.web.WebModuleUtil;
24
import org.openmrs.scheduler.SchedulerUtil;
25
import org.openmrs.util.DatabaseUpdateException;
26
import org.openmrs.util.DatabaseUpdater;
27
import org.openmrs.util.InputRequiredException;
28
import org.openmrs.util.MemoryLeakUtil;
29
import org.openmrs.util.OpenmrsClassLoader;
30
import org.openmrs.util.OpenmrsConstants;
31
import org.openmrs.util.OpenmrsUtil;
32
import org.openmrs.web.filter.initialization.DatabaseDetective;
33
import org.openmrs.web.filter.initialization.InitializationFilter;
34
import org.openmrs.web.filter.update.UpdateFilter;
35
import org.owasp.csrfguard.CsrfGuard;
36
import org.owasp.csrfguard.CsrfGuardServletContextListener;
37
import org.slf4j.LoggerFactory;
38
import org.slf4j.MarkerFactory;
39
import org.springframework.beans.factory.BeanCreationException;
40
import org.springframework.util.StringUtils;
41
import org.springframework.web.context.ContextLoader;
42
import org.springframework.web.context.WebApplicationContext;
43
import org.springframework.web.context.support.XmlWebApplicationContext;
44
import org.w3c.dom.Document;
45
import org.w3c.dom.Element;
46
import org.xml.sax.InputSource;
47

48
import javax.servlet.ServletContext;
49
import javax.servlet.ServletContextEvent;
50
import javax.servlet.ServletContextListener;
51
import javax.servlet.ServletException;
52
import javax.servlet.http.HttpSessionEvent;
53
import javax.servlet.http.HttpSessionListener;
54
import javax.xml.parsers.DocumentBuilder;
55
import javax.xml.parsers.DocumentBuilderFactory;
56
import java.io.File;
57
import java.io.FileInputStream;
58
import java.io.FileOutputStream;
59
import java.io.IOException;
60
import java.io.InputStream;
61
import java.io.OutputStreamWriter;
62
import java.io.StringReader;
63
import java.lang.reflect.Field;
64
import java.nio.charset.StandardCharsets;
65
import java.nio.file.Files;
66
import java.nio.file.Paths;
67
import java.sql.Driver;
68
import java.sql.DriverManager;
69
import java.util.ArrayList;
70
import java.util.Collection;
71
import java.util.Collections;
72
import java.util.Enumeration;
73
import java.util.HashMap;
74
import java.util.List;
75
import java.util.Map;
76
import java.util.Properties;
77

78
/**
79
 * Our Listener class performs the basic starting functions for our webapp. Basic needs for starting
80
 * the API: 1) Get the runtime properties 2) Start Spring 3) Start the OpenMRS APi (via
81
 * Context.startup) Basic startup needs specific to the web layer: 1) Do the web startup of the
82
 * modules 2) Copy the custom look/images/messages over into the web layer
83
 */
84
public final class Listener extends ContextLoader implements ServletContextListener, HttpSessionListener {
×
85
        
86
        private static final org.slf4j.Logger log = LoggerFactory.getLogger(Listener.class);
1✔
87
        
88
        private static boolean runtimePropertiesFound = false;
1✔
89
        
90
        private static Throwable errorAtStartup = null;
1✔
91
        
92
        private static boolean setupNeeded = false;
1✔
93
        
94
        private static boolean openmrsStarted = false;
1✔
95
        
96
        /**
97
         * Boolean flag set on webapp startup marking whether there is a runtime properties file or not.
98
         * If there is not, then the {@link InitializationFilter} takes over any openmrs url and
99
         * redirects to the {@link WebConstants#SETUP_PAGE_URL}
100
         *
101
         * @return true/false whether an openmrs runtime properties file is defined
102
         */
103
        public static boolean runtimePropertiesFound() {
104
                return runtimePropertiesFound;
×
105
        }
106
        
107
        /**
108
         * Boolean flag set by the {@link #contextInitialized(ServletContextEvent)} method if an error
109
         * occurred when trying to start up. The StartupErrorFilter displays the error to the admin
110
         *
111
         * @return true/false if an error occurred when starting up
112
         */
113
        public static boolean errorOccurredAtStartup() {
114
                return errorAtStartup != null;
1✔
115
        }
116
        
117
        /**
118
         * Boolean flag that tells if we need to run the database setup wizard.
119
         *
120
         * @return true if setup is needed, else false.
121
         */
122
        public static boolean isSetupNeeded() {
123
                return setupNeeded;
×
124
        }
125
        
126
        /**
127
         * Boolean flag that tells if OpenMRS is started and ready to handle requests via REST.
128
         *
129
         * @return true if started, else false.
130
         */
131
        public static boolean isOpenmrsStarted() {
132
                return openmrsStarted;
×
133
        }
134
        
135
        /**
136
         * Get the error thrown at startup
137
         *
138
         * @return get the error thrown at startup
139
         */
140
        public static Throwable getErrorAtStartup() {
141
                return errorAtStartup;
1✔
142
        }
143
        
144
        public static void setRuntimePropertiesFound(boolean runtimePropertiesFound) {
145
                Listener.runtimePropertiesFound = runtimePropertiesFound;
×
146
        }
×
147
        
148
        public static void setErrorAtStartup(Throwable errorAtStartup) {
149
                Listener.errorAtStartup = errorAtStartup;
×
150
        }
×
151

152
        /**
153
         * This gets all Spring components that implement HttpSessionListener 
154
         * and passes the HttpSession event to them whenever an HttpSession is created
155
         * @see HttpSessionListener#sessionCreated(HttpSessionEvent) 
156
         */
157
        @Override
158
        public void sessionCreated(HttpSessionEvent se) {
159
                for (HttpSessionListener listener : getHttpSessionListeners()) {
×
160
                        listener.sessionCreated(se);
×
161
                }
×
162
        }
×
163

164
        /**
165
         *         This gets all Spring components that implement HttpSessionListener 
166
         *         and passes the HttpSession event to them whenever an HttpSession is destroyed
167
         * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
168
         */
169
        @Override
170
        public void sessionDestroyed(HttpSessionEvent se) {
171
                for (HttpSessionListener listener : getHttpSessionListeners()) {
×
172
                        listener.sessionDestroyed(se);
×
173
                }
×
174
        }
×
175

176
        /**
177
         *         This retrieves all Spring components that implement HttpSessionListener
178
         *         If an exception is thrown trying to retrieve these beans from the Context, a warning is logged
179
         * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
180
         */
181
        private List<HttpSessionListener> getHttpSessionListeners() {
182
                List<HttpSessionListener> httpSessionListeners = Collections.emptyList();
×
183
                
184
                if (openmrsStarted) {
×
185
                        try {
186
                                httpSessionListeners = Context.getRegisteredComponents(HttpSessionListener.class);
×
187
                        }
188
                        catch (Exception e) {
×
189
                                log.warn("An error occurred trying to retrieve HttpSessionListener beans from the context", e);
×
190
                        }
×
191
                }
192
                
193
                return httpSessionListeners;
×
194
        }
195

196
        /**
197
         * This method is called when the servlet context is initialized(when the Web Application is
198
         * deployed). You can initialize servlet context related data here.
199
         *
200
         * @param event
201
         */
202
        @Override
203
        public void contextInitialized(ServletContextEvent event) {
204
                log.debug("Starting the OpenMRS webapp");
×
205
                
206
                try {
207
                        // validate the current JVM version
208
                        OpenmrsUtil.validateJavaVersion();
×
209
                        
210
                        ServletContext servletContext = event.getServletContext();
×
211
                        
212
                        // pulled from web.xml.
213
                        loadConstants(servletContext);
×
214
                        
215
                        // erase things in the dwr file
216
                        clearDWRFile(servletContext);
×
217
                        
218
                        setApplicationDataDirectory(servletContext);
×
219
                        
220
                        
221
                        // Try to get the runtime properties
222
                        Properties props = getRuntimeProperties();
×
223
                        if (props != null) {
×
224
                                // the user has defined a runtime properties file
225
                                setRuntimePropertiesFound(true);
×
226
                                // set props to the context so that they can be
227
                                // used during sessionFactory creation
228
                                Context.setRuntimeProperties(props);
×
229
                                
230
                                String appDataRuntimeProperty = props
×
231
                                        .getProperty(OpenmrsConstants.APPLICATION_DATA_DIRECTORY_RUNTIME_PROPERTY, null);
×
232
                                if (StringUtils.hasLength(appDataRuntimeProperty)) {
×
233
                                        OpenmrsUtil.setApplicationDataDirectory(null);
×
234
                                }
235
                                //ensure that we always log the runtime properties file that we are using
236
                                //since openmrs is just booting, the log levels are not yet set. TRUNK-4835
NEW
237
                                OpenmrsLoggingUtil.applyLogLevel(getClass().getName(), "INFO");
×
238
                                log.info("Using runtime properties file: {}",
×
239
                                         OpenmrsUtil.getRuntimePropertiesFilePathName(WebConstants.WEBAPP_NAME));
×
240
                        }
241

242
                        loadCsrfGuardProperties(servletContext);
×
243
                        
244
                        Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
×
245
                        
246
                        if (!setupNeeded()) {
×
247
                                // must be done after the runtime properties are
248
                                // found but before the database update is done
249
                                copyCustomizationIntoWebapp(servletContext, props);
×
250
                                
251
                                /**
252
                                 * This logic is from ContextLoader.initWebApplicationContext. Copied here instead
253
                                 * of calling that so that the context is not cached and hence not garbage collected
254
                                 */
255
                                XmlWebApplicationContext context = (XmlWebApplicationContext) createWebApplicationContext(servletContext);
×
256
                                configureAndRefreshWebApplicationContext(context, servletContext);
×
257
                                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
×
258
                                
259
                                WebDaemon.startOpenmrs(event.getServletContext());
×
260
                        } else {
×
261
                                setupNeeded = true;
×
262
                        }
263
                        
264
                }
265
                catch (Exception e) {
×
266
                        setErrorAtStartup(e);
×
267
                        log.error(MarkerFactory.getMarker("FATAL"), "Failed to obtain JDBC connection", e);
×
268
                }
×
269
        }
×
270

271
        private void loadCsrfGuardProperties(ServletContext servletContext) throws IOException {
272
                File csrfGuardFile = new File(OpenmrsUtil.getApplicationDataDirectory(), "csrfguard.properties");
×
273
                Properties csrfGuardProperties = new Properties();
×
274
                if (csrfGuardFile.exists()) {
×
275
                        try (InputStream csrfGuardInputStream = Files.newInputStream(csrfGuardFile.toPath())) {
×
276
                                csrfGuardProperties.load(csrfGuardInputStream);
×
277
                        }
278
                        catch (Exception e) {
×
279
                                log.error("Error loading csrfguard.properties file at " + csrfGuardFile.getAbsolutePath(), e);
×
280
                                throw e;
×
281
                        }
×
282
                }
283
                else {
284
                        String fileName = servletContext.getRealPath("/WEB-INF/csrfguard.properties");
×
285
                        try (InputStream csrfGuardInputStream = Files.newInputStream(Paths.get(fileName))) {
×
286
                                csrfGuardProperties.load(csrfGuardInputStream);
×
287
                        }
288
                        catch (Exception e) {
×
289
                                log.error("Error loading csrfguard.properties file at " +  fileName, e);
×
290
                                throw e;
×
291
                        }
×
292
                }
293
                
294
                Properties runtimeProperties = getRuntimeProperties();
×
295
                if (runtimeProperties != null) {
×
296
                        runtimeProperties.stringPropertyNames().forEach(property -> {
×
297
                                if (property.startsWith("org.owasp.csrfguard")) {
×
298
                                        csrfGuardProperties.setProperty(property, runtimeProperties.getProperty(property));
×
299
                                }
300
                        });        
×
301
                }
302
                
303
                CsrfGuard.load(csrfGuardProperties);
×
304
                
305
                try {
306
                        //CSRFGuard by default loads properties using CsrfGuardServletContextListener
307
                        //which sets the servlet context path to be used during variable substitution of
308
                        //%servletContext% in the properties file.
309
                        Field field = CsrfGuardServletContextListener.class.getDeclaredField("servletContext");
×
310
                        field.setAccessible(true);
×
311
                        field.set(null, servletContext.getContextPath());
×
312
                }
313
                catch (Exception ex) {
×
314
                        log.error("Failed to set the CSRFGuard servlet context", ex);
×
315
                }
×
316
        }
×
317
        
318
        /**
319
         * This method knows about all the filters that openmrs uses for setup. Currently those are the
320
         * {@link InitializationFilter} and the {@link UpdateFilter}. If either of these have to do
321
         * something, openmrs won't start in this Listener.
322
         *
323
         * @return true if one of the filters needs to take some action
324
         */
325
        private boolean setupNeeded() throws Exception {
326
                if (!runtimePropertiesFound) {
×
327
                        return true;
×
328
                }
329
                
330
                DatabaseDetective databaseDetective = new DatabaseDetective();
×
331
                if (databaseDetective.isDatabaseEmpty(OpenmrsUtil.getRuntimeProperties(WebConstants.WEBAPP_NAME))) {
×
332
                        return true;
×
333
                }
334
                
335
                return DatabaseUpdater.updatesRequired() && !DatabaseUpdater.allowAutoUpdate();
×
336
        }
337
        
338
        /**
339
         * Do the work of starting openmrs.
340
         *
341
         * @param servletContext
342
         * @throws ServletException
343
         */
344
        public static void startOpenmrs(ServletContext servletContext) throws ServletException {
345
                openmrsStarted = false;
1✔
346
                // start openmrs
347
                try {
348
                        // load bundled modules that are packaged into the webapp
349
                        Listener.loadBundledModules(servletContext);
×
350
                        
351
                        Context.startup(getRuntimeProperties());
×
352
                }
353
                catch (DatabaseUpdateException | InputRequiredException updateEx) {
×
354
                        throw new ServletException("Should not be here because updates were run previously", updateEx);
×
355
                }
356
                catch (MandatoryModuleException mandatoryModEx) {
×
357
                        throw new ServletException(mandatoryModEx);
×
358
                }
359
                catch (OpenmrsCoreModuleException coreModEx) {
×
360
                        // don't wrap this error in a ServletException because we want to deal with it differently
361
                        // in the StartupErrorFilter class
362
                        throw coreModEx;
×
363
                }
×
364
                
365
                // TODO catch openmrs errors here and drop the user back out to the setup screen
366
                
367
                try {
368
                        
369
                        // web load modules
370
                        Listener.performWebStartOfModules(servletContext);
×
371
                        
372
                        // start the scheduled tasks
373
                        SchedulerUtil.startup(getRuntimeProperties());
×
374
                }
375
                catch (Exception t) {
×
376
                        Context.shutdown();
×
377
                        WebModuleUtil.shutdownModules(servletContext);
×
378
                        throw new ServletException(t);
×
379
                }
380
                finally {
381
                        Context.closeSession();
×
382
                }
383
                openmrsStarted = true;
×
384
        }
×
385
        
386
        /**
387
         * Load the openmrs constants with values from web.xml init parameters
388
         *
389
         * @param servletContext startup context (web.xml)
390
         */
391
        private void loadConstants(ServletContext servletContext) {
392
                WebConstants.BUILD_TIMESTAMP = servletContext.getInitParameter("build.timestamp");
×
393
                WebConstants.WEBAPP_NAME = getContextPath(servletContext);
×
394
                WebConstants.MODULE_REPOSITORY_URL = servletContext.getInitParameter("module.repository.url");
×
395
                
396
                if (!"openmrs".equalsIgnoreCase(WebConstants.WEBAPP_NAME)) {
×
397
                        OpenmrsConstants.KEY_OPENMRS_APPLICATION_DATA_DIRECTORY = WebConstants.WEBAPP_NAME
×
398
                                + "_APPLICATION_DATA_DIRECTORY";
399
                }
400
        }
×
401
        
402
        private void setApplicationDataDirectory(ServletContext servletContext) {
403
                // note: the below value will be overridden after reading the runtime properties if the
404
                // "application_data_directory" runtime property is set
405
                String appDataDir = servletContext.getInitParameter("application.data.directory");
×
406
                if (StringUtils.hasLength(appDataDir)) {
×
407
                        OpenmrsUtil.setApplicationDataDirectory(appDataDir);
×
408
                } else if (!"openmrs".equalsIgnoreCase(WebConstants.WEBAPP_NAME)) {
×
409
                        OpenmrsUtil.setApplicationDataDirectory(
×
410
                            Paths.get(OpenmrsUtil.getApplicationDataDirectory(), WebConstants.WEBAPP_NAME).toString());
×
411
                }
412
        }
×
413
        
414
        /**
415
         * @return current contextPath of this webapp without initial slash
416
         */
417
        private String getContextPath(ServletContext servletContext) {
418
                // Get the context path without the request.
419
                String contextPath = servletContext.getContextPath();
×
420
                
421
                // trim off initial slash if it exists
422
                if (contextPath.startsWith("/")) {
×
423
                        contextPath = contextPath.substring(1);
×
424
                }
425
                
426
                return contextPath;
×
427
        }
428
        
429
        /**
430
         * Convenience method to empty out the dwr-modules.xml file to fix any errors that might have
431
         * occurred in it when loading or unloading modules.
432
         *
433
         * @param servletContext
434
         */
435
        private void clearDWRFile(ServletContext servletContext) {
436
                File dwrFile = Paths.get(servletContext.getRealPath(""), "WEB-INF", "dwr-modules.xml").toFile();
×
437
                
438
                try {
439
                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
×
440
                        DocumentBuilder db = dbf.newDocumentBuilder();
×
441
                        // When asked to resolve external entities (such as a DTD) we return an InputSource
442
                        // with no data at the end, causing the parser to ignore the DTD.
443
                        db.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
×
444
                        Document doc = db.parse(dwrFile);
×
445
                        Element elem = doc.getDocumentElement();
×
446
                        elem.setTextContent("");
×
447
                        OpenmrsUtil.saveDocument(doc, dwrFile);
×
448
                }
449
                catch (Exception e) {
×
450
                        // got here because the dwr-modules.xml file is empty for some reason.  This might
451
                        // happen because the servlet container (i.e. tomcat) crashes when first loading this file
452
                        log.debug("Error clearing dwr-modules.xml", e);
×
453
                        dwrFile.delete();
×
454
                        OutputStreamWriter writer = null;
×
455
                        try {
456
                                writer = new OutputStreamWriter(new FileOutputStream(dwrFile), StandardCharsets.UTF_8);
×
457
                                writer.write(
×
458
                                    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE dwr PUBLIC \"-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN\" \"http://directwebremoting.org/schema/dwr20.dtd\">\n<dwr></dwr>");
459
                        }
460
                        catch (IOException io) {
×
461
                                log.error(
×
462
                                    "Unable to clear out the " + dwrFile.getAbsolutePath() + " file.  Please redeploy the openmrs war file",
×
463
                                    io);
464
                        }
465
                        finally {
466
                                if (writer != null) {
×
467
                                        try {
468
                                                writer.close();
×
469
                                        }
470
                                        catch (IOException io) {
×
471
                                                log.warn("Couldn't close Writer: " + io);
×
472
                                        }
×
473
                                }
474
                        }
475
                }
×
476
        }
×
477
        
478
        /**
479
         * Copy the customization scripts over into the webapp
480
         *
481
         * @param servletContext
482
         */
483
        private void copyCustomizationIntoWebapp(ServletContext servletContext, Properties props) {
484
                String realPath = servletContext.getRealPath("");
×
485
                // TODO centralize map to WebConstants?
486
                Map<String, String> custom = new HashMap<>();
×
487
                custom.put("custom.template.dir", "/WEB-INF/template");
×
488
                custom.put("custom.index.jsp.file", "/WEB-INF/view/index.jsp");
×
489
                custom.put("custom.login.jsp.file", "/WEB-INF/view/login.jsp");
×
490
                custom.put("custom.patientDashboardForm.jsp.file", "/WEB-INF/view/patientDashboardForm.jsp");
×
491
                custom.put("custom.images.dir", "/images");
×
492
                custom.put("custom.style.css.file", "/style.css");
×
493
                custom.put("custom.messages", "/WEB-INF/custom_messages.properties");
×
494
                custom.put("custom.messages_fr", "/WEB-INF/custom_messages_fr.properties");
×
495
                custom.put("custom.messages_es", "/WEB-INF/custom_messages_es.properties");
×
496
                custom.put("custom.messages_de", "/WEB-INF/custom_messages_de.properties");
×
497
                
498
                for (Map.Entry<String, String> entry : custom.entrySet()) {
×
499
                        String prop = entry.getKey();
×
500
                        String webappPath = entry.getValue();
×
501
                        String userOverridePath = props.getProperty(prop);
×
502
                        // if they defined the variable
503
                        if (userOverridePath != null) {
×
504
                                String absolutePath = realPath + webappPath;
×
505
                                File file = new File(userOverridePath);
×
506
                                
507
                                // if they got the path correct
508
                                // also, if file does not start with a "." (hidden files, like SVN files)
509
                                if (file.exists() && !userOverridePath.startsWith(".")) {
×
510
                                        log.debug("Overriding file: " + absolutePath);
×
511
                                        log.debug("Overriding file with: " + userOverridePath);
×
512
                                        if (file.isDirectory()) {
×
513
                                                File[] files = file.listFiles();
×
514
                                                if (files != null) {
×
515
                                                        for (File f : files) {
×
516
                                                                userOverridePath = f.getAbsolutePath();
×
517
                                                                if (!f.getName().startsWith(".")) {
×
518
                                                                        String tmpAbsolutePath = absolutePath + "/" + f.getName();
×
519
                                                                        if (!copyFile(userOverridePath, tmpAbsolutePath)) {
×
520
                                                                                log.warn("Unable to copy file in folder defined by runtime property: " + prop);
×
521
                                                                                log.warn("Your source directory (or a file in it) '" + userOverridePath
×
522
                                                                                                        + " cannot be loaded or destination '" + tmpAbsolutePath + "' cannot be found");
523
                                                                        }
524
                                                                }
525
                                                        }
526
                                                }
527
                                        } else {
×
528
                                                // file is not a directory
529
                                                if (!copyFile(userOverridePath, absolutePath)) {
×
530
                                                        log.warn("Unable to copy file defined by runtime property: " + prop);
×
531
                                                        log.warn("Your source file '" + userOverridePath + " cannot be loaded or destination '"
×
532
                                                                + absolutePath + "' cannot be found");
533
                                                }
534
                                        }
535
                                }
536
                        }
537
                        
538
                }
×
539
        }
×
540
        
541
        /**
542
         * Copies file pointed to by <code>fromPath</code> to <code>toPath</code>
543
         *
544
         * @param fromPath
545
         * @param toPath
546
         * @return true/false whether the copy was a success
547
         */
548
        private boolean copyFile(String fromPath, String toPath) {
549
                FileInputStream inputStream = null;
×
550
                FileOutputStream outputStream = null;
×
551
                try {
552
                        inputStream = new FileInputStream(fromPath);
×
553
                        outputStream = new FileOutputStream(toPath);
×
554
                        OpenmrsUtil.copyFile(inputStream, outputStream);
×
555
                }
556
                catch (IOException io) {
×
557
                        return false;
×
558
                }
559
                finally {
560
                        try {
561
                                if (inputStream != null) {
×
562
                                        inputStream.close();
×
563
                                }
564
                        }
565
                        catch (IOException io) {
×
566
                                log.warn("Unable to close input stream", io);
×
567
                        }
×
568
                        try {
569
                                if (outputStream != null) {
×
570
                                        outputStream.close();
×
571
                                }
572
                        }
573
                        catch (IOException io) {
×
574
                                log.warn("Unable to close input stream", io);
×
575
                        }
×
576
                }
577
                return true;
×
578
        }
579
        
580
        /**
581
         * Load the pre-packaged modules from web/WEB-INF/bundledModules. <br>
582
         * <br>
583
         * This method assumes that the api startup() and WebModuleUtil.startup() will be called later
584
         * for modules that loaded here
585
         *
586
         * @param servletContext the current servlet context for the webapp
587
         */
588
        public static void loadBundledModules(ServletContext servletContext) {
589
                File folder = Paths.get(servletContext.getRealPath(""), "WEB-INF", "bundledModules").toFile();
×
590
                
591
                if (!folder.exists()) {
×
592
                        log.warn("Bundled module folder doesn't exist: " + folder.getAbsolutePath());
×
593
                        return;
×
594
                }
595
                if (!folder.isDirectory()) {
×
596
                        log.warn("Bundled module folder isn't really a directory: " + folder.getAbsolutePath());
×
597
                        return;
×
598
                }
599
                
600
                // loop over the modules and load the modules that we can
601
                File[] files = folder.listFiles();
×
602
                if (files != null) {
×
603
                        for (File f : files) {
×
604
                                if (!f.getName().startsWith(".")) { // ignore .svn folder and the like
×
605
                                        try {
606
                                                Module mod = ModuleFactory.loadModule(f);
×
607
                                                log.debug("Loaded bundled module: " + mod + " successfully");
×
608
                                        }
609
                                        catch (Exception e) {
×
610
                                                log.warn("Error while trying to load bundled module " + f.getName() + "", e);
×
611
                                        }
×
612
                                }
613
                        }
614
                }
615
        }
×
616
        
617
        /**
618
         * Called when the webapp is shut down properly Must call Context.shutdown() and then shutdown
619
         * all the web layers of the modules
620
         *
621
         * @see org.springframework.web.context.ContextLoaderListener#contextDestroyed(javax.servlet.ServletContextEvent)
622
         */
623
        @SuppressWarnings("squid:S1215")
624
        @Override
625
        public void contextDestroyed(ServletContextEvent event) {
626
                
627
                try {
628
                        openmrsStarted = false;
×
629
                        Context.openSession();
×
630
                        
631
                        Context.shutdown();
×
632
                        
633
                        WebModuleUtil.shutdownModules(event.getServletContext());
×
634
                        
635
                }
636
                catch (Exception e) {
×
637
                        // don't print the unhelpful "contextDAO is null" message
638
                        if (!"contextDAO is null".equals(e.getMessage())) {
×
639
                                // not using log.error here so it can be garbage collected
640
                                System.out.println("Listener.contextDestroyed: Error while shutting down openmrs: ");
×
641
                                log.error("Listener.contextDestroyed: Error while shutting down openmrs: ", e);
×
642
                        }
643
                }
644
                finally {
645
                        if ("true".equalsIgnoreCase(System.getProperty("FUNCTIONAL_TEST_MODE"))) {
×
646
                                //Delete the temporary file created for functional testing and shutdown the mysql daemon
647
                                String filename = WebConstants.WEBAPP_NAME + "-test-runtime.properties";
×
648
                                File file = new File(OpenmrsUtil.getApplicationDataDirectory(), filename);
×
649
                                System.out.println(filename + " delete=" + file.delete());
×
650
                                
651
                        }
652
                        // remove the user context that we set earlier
653
                        Context.closeSession();
×
654
                }
655
                try {
656
                        for (Enumeration<Driver> e = DriverManager.getDrivers(); e.hasMoreElements();) {
×
657
                                Driver driver = e.nextElement();
×
658
                                ClassLoader classLoader = driver.getClass().getClassLoader();
×
659
                                // only unload drivers for this webapp
660
                                if (classLoader == null || classLoader == getClass().getClassLoader()) {
×
661
                                        DriverManager.deregisterDriver(driver);
×
662
                                } else {
663
                                        System.err.println("Didn't remove driver class: " + driver.getClass() + " with classloader of: "
×
664
                                                + driver.getClass().getClassLoader());
×
665
                                }
666
                        }
×
667
                }
668
                catch (Exception e) {
×
669
                        System.err.println("Listener.contextDestroyed: Failed to cleanup drivers in webapp");
×
670
                        log.error("Listener.contextDestroyed: Failed to cleanup drivers in webapp", e);
×
671
                }
×
672
                
673
                MemoryLeakUtil.shutdownMysqlCancellationTimer();
×
674
                
675
                OpenmrsClassLoader.onShutdown();
×
676
                
677
                LogManager.shutdown();
×
678
                
679
                // just to make things nice and clean.
680
                // Suppressing sonar issue squid:S1215
681
                System.gc();
×
682
                System.gc();
×
683
        }
×
684
        
685
        /**
686
         * Finds and loads the runtime properties
687
         *
688
         * @return Properties
689
         * @see OpenmrsUtil#getRuntimeProperties(String)
690
         */
691
        public static Properties getRuntimeProperties() {
692
                return OpenmrsUtil.getRuntimeProperties(WebConstants.WEBAPP_NAME);
×
693
        }
694
        
695
        /**
696
         * Call WebModuleUtil.startModule on each started module
697
         *
698
         * @param servletContext
699
         * @throws ModuleMustStartException if the context cannot restart due to a
700
         *             {@link MandatoryModuleException} or {@link OpenmrsCoreModuleException}
701
         */
702
        public static void performWebStartOfModules(ServletContext servletContext) throws ModuleMustStartException, Exception {
703
                List<Module> startedModules = new ArrayList<>(ModuleFactory.getStartedModules());
×
704
                performWebStartOfModules(startedModules, servletContext);
×
705
        }
×
706
        
707
        public static void performWebStartOfModules(Collection<Module> startedModules, ServletContext servletContext)
708
                throws ModuleMustStartException, Exception {
709
                
710
                boolean someModuleNeedsARefresh = false;
×
711
                for (Module mod : startedModules) {
×
712
                        try {
713
                                boolean thisModuleCausesRefresh = WebModuleUtil.startModule(mod, servletContext,
×
714
                                    /* delayContextRefresh */true);
715
                                someModuleNeedsARefresh = someModuleNeedsARefresh || thisModuleCausesRefresh;
×
716
                        }
717
                        catch (Exception e) {
×
718
                                mod.setStartupErrorMessage("Unable to start module", e);
×
719
                        }
×
720
                }
×
721
                
722
                if (someModuleNeedsARefresh) {
×
723
                        try {
724
                                WebModuleUtil.refreshWAC(servletContext, true, null);
×
725
                        }
726
                        catch (ModuleMustStartException | BeanCreationException ex) {
×
727
                                // pass this up to the calling method so that openmrs loading stops
728
                                throw ex;
×
729
                        }
730
                        catch (Exception e) {
×
731
                                Throwable rootCause = getActualRootCause(e, true);
×
732
                                if (rootCause != null) {
×
733
                                        log.error(MarkerFactory.getMarker("FATAL"),
×
734
                                            "Unable to refresh the spring application context.  Root Cause was:", rootCause);
735
                                } else {
736
                                        log.error(MarkerFactory.getMarker("FATAL"),
×
737
                                            "nable to refresh the spring application context. Unloading all modules,  Error was:", e);
738
                                }
739
                                
740
                                try {
741
                                        WebModuleUtil.shutdownModules(servletContext);
×
742
                                        for (Module mod : ModuleFactory.getLoadedModules()) {// use loadedModules to avoid a concurrentmodificationexception
×
743
                                                if (!mod.isCoreModule() && !mod.isMandatory()) {
×
744
                                                        try {
745
                                                                ModuleFactory.stopModule(mod, true, true);
×
746
                                                        }
747
                                                        catch (Exception t3) {
×
748
                                                                // just keep going if we get an error shutting down.  was probably caused by the module
749
                                                                // that actually got us to this point!
750
                                                                log.trace("Unable to shutdown module:" + mod, t3);
×
751
                                                        }
×
752
                                                }
753
                                        }
×
754
                                        WebModuleUtil.refreshWAC(servletContext, true, null);
×
755
                                }
756
                                catch (MandatoryModuleException ex) {
×
757
                                        // pass this up to the calling method so that openmrs loading stops
758
                                        throw new MandatoryModuleException(ex.getModuleId(), "Got an error while starting a mandatory module: "
×
759
                                                + e.getMessage() + ". Check the server logs for more information");
×
760
                                }
761
                                catch (Exception t2) {
×
762
                                        // a mandatory or core module is causing spring to fail to start up.  We don't want those
763
                                        // stopped so we must report this error to the higher authorities
764
                                        log.warn("caught another error: ", t2);
×
765
                                        throw t2;
×
766
                                }
×
767
                        }
×
768
                }
769
                
770
                // because we delayed the refresh, we need to load+start all servlets and filters now
771
                // (this is to protect servlets/filters that depend on their module's spring xml config being available)
772
                for (Module mod : ModuleFactory.getStartedModulesInOrder()) {
×
773
                        WebModuleUtil.loadServlets(mod, servletContext);
×
774
                        WebModuleUtil.loadFilters(mod, servletContext);
×
775
                }
×
776
                servletContext.setAttribute(OpenmrsJspServlet.OPENMRS_TLD_SCAN_NEEDED, true);
×
777
        }
×
778
        
779
        /**
780
         * Convenience method that recursively attempts to pull the root case from a Throwable
781
         *
782
         * @param t the Throwable object
783
         * @param isOriginalError specifies if the passed in Throwable is the original Exception that
784
         *            was thrown
785
         * @return the root cause if any was found
786
         */
787
        private static Throwable getActualRootCause(Throwable t, boolean isOriginalError) {
788
                if (t.getCause() != null) {
×
789
                        return getActualRootCause(t.getCause(), false);
×
790
                }
791
                
792
                if (!isOriginalError) {
×
793
                        return t;
×
794
                }
795
                
796
                return null;
×
797
        }
798
        
799
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc