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

openmrs / openmrs-core / 23193642646

17 Mar 2026 12:13PM UTC coverage: 63.1% (-0.3%) from 63.429%
23193642646

push

github

rkorytkowski
Fixing: Fix an issue with the ModuleResourceServlet

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

925 existing lines in 17 files now uncovered.

23137 of 36667 relevant lines covered (63.1%)

0.63 hits per line

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

3.45
/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 javax.servlet.ServletContext;
13
import javax.servlet.ServletContextEvent;
14
import javax.servlet.ServletContextListener;
15
import javax.servlet.ServletException;
16
import javax.servlet.http.HttpSessionEvent;
17
import javax.servlet.http.HttpSessionListener;
18
import javax.xml.parsers.DocumentBuilder;
19
import javax.xml.parsers.DocumentBuilderFactory;
20
import java.io.File;
21
import java.io.FileInputStream;
22
import java.io.FileOutputStream;
23
import java.io.IOException;
24
import java.io.InputStream;
25
import java.io.OutputStreamWriter;
26
import java.io.StringReader;
27
import java.lang.reflect.Field;
28
import java.nio.charset.StandardCharsets;
29
import java.nio.file.Files;
30
import java.nio.file.Paths;
31
import java.sql.Driver;
32
import java.sql.DriverManager;
33
import java.util.ArrayList;
34
import java.util.Collection;
35
import java.util.Collections;
36
import java.util.Enumeration;
37
import java.util.HashMap;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.Properties;
41

42
import org.apache.logging.log4j.LogManager;
43
import org.openmrs.api.context.Context;
44
import org.openmrs.module.MandatoryModuleException;
45
import org.openmrs.module.Module;
46
import org.openmrs.module.ModuleFactory;
47
import org.openmrs.module.ModuleMustStartException;
48
import org.openmrs.module.web.OpenmrsJspServlet;
49
import org.openmrs.module.web.WebModuleUtil;
50
import org.openmrs.util.DatabaseUpdateException;
51
import org.openmrs.util.DatabaseUpdater;
52
import org.openmrs.util.InputRequiredException;
53
import org.openmrs.util.OpenmrsClassLoader;
54
import org.openmrs.util.OpenmrsConstants;
55
import org.openmrs.util.OpenmrsUtil;
56
import org.openmrs.web.filter.initialization.DatabaseDetective;
57
import org.openmrs.web.filter.initialization.InitializationFilter;
58
import org.openmrs.web.filter.update.UpdateFilter;
59
import org.owasp.csrfguard.CsrfGuard;
60
import org.owasp.csrfguard.CsrfGuardServletContextListener;
61
import org.slf4j.LoggerFactory;
62
import org.slf4j.MarkerFactory;
63
import org.springframework.beans.factory.BeanCreationException;
64
import org.springframework.util.StringUtils;
65
import org.springframework.web.context.ContextLoader;
66
import org.springframework.web.context.WebApplicationContext;
67
import org.springframework.web.context.support.XmlWebApplicationContext;
68
import org.w3c.dom.Document;
69
import org.w3c.dom.Element;
70
import org.xml.sax.InputSource;
71

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

146
        /**
147
         * This gets all Spring components that implement HttpSessionListener 
148
         * and passes the HttpSession event to them whenever an HttpSession is created
149
         * @see HttpSessionListener#sessionCreated(HttpSessionEvent) 
150
         */
151
        @Override
152
        public void sessionCreated(HttpSessionEvent se) {
UNCOV
153
                for (HttpSessionListener listener : getHttpSessionListeners()) {
×
UNCOV
154
                        listener.sessionCreated(se);
×
155
                }
×
156
        }
×
157

158
        /**
159
         *         This gets all Spring components that implement HttpSessionListener 
160
         *         and passes the HttpSession event to them whenever an HttpSession is destroyed
161
         * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
162
         */
163
        @Override
164
        public void sessionDestroyed(HttpSessionEvent se) {
UNCOV
165
                for (HttpSessionListener listener : getHttpSessionListeners()) {
×
UNCOV
166
                        listener.sessionDestroyed(se);
×
167
                }
×
168
        }
×
169

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

190
        /**
191
         * This method is called when the servlet context is initialized(when the Web Application is
192
         * deployed). You can initialize servlet context related data here.
193
         *
194
         * @param event
195
         */
196
        @Override
197
        public void contextInitialized(ServletContextEvent event) {
UNCOV
198
                log.debug("Starting the OpenMRS webapp");
×
199
                
200
                try {
201
                        // validate the current JVM version
UNCOV
202
                        OpenmrsUtil.validateJavaVersion();
×
203
                        
204
                        ServletContext servletContext = event.getServletContext();
×
205
                        
206
                        // pulled from web.xml.
UNCOV
207
                        loadConstants(servletContext);
×
208
                        
209
                        // erase things in the dwr file
UNCOV
210
                        clearDWRFile(servletContext);
×
211
                        
212
                        setApplicationDataDirectory(servletContext);
×
213
                        
214
                        
215
                        // Try to get the runtime properties
UNCOV
216
                        Properties props = getRuntimeProperties();
×
UNCOV
217
                        if (props != null) {
×
218
                                // the user has defined a runtime properties file
219
                                setRuntimePropertiesFound(true);
×
220
                                // set props to the context so that they can be
221
                                // used during sessionFactory creation
UNCOV
222
                                Context.setRuntimeProperties(props);
×
223
                                
224
                                String appDataRuntimeProperty = props
×
UNCOV
225
                                        .getProperty(OpenmrsConstants.APPLICATION_DATA_DIRECTORY_RUNTIME_PROPERTY, null);
×
226
                                if (StringUtils.hasLength(appDataRuntimeProperty)) {
×
227
                                        OpenmrsUtil.setApplicationDataDirectory(null);
×
228
                                }
229
                                log.warn("Using runtime properties file: {}",
×
UNCOV
230
                                         OpenmrsUtil.getRuntimePropertiesFilePathName(WebConstants.WEBAPP_NAME));
×
231
                        }
232

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

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