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

openmrs / openmrs-core / 18188864354

02 Oct 2025 09:19AM UTC coverage: 64.889% (-0.04%) from 64.931%
18188864354

push

github

rkorytkowski
TRUNK-6436: Add logging to monitor startup performance

(cherry picked from commit ece973daa)

2 of 29 new or added lines in 4 files covered. (6.9%)

16 existing lines in 8 files now uncovered.

23423 of 36097 relevant lines covered (64.89%)

0.65 hits per line

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

34.57
/web/src/main/java/org/openmrs/module/web/WebModuleUtil.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.module.web;
11

12
import java.io.File;
13
import java.io.FileInputStream;
14
import java.io.FileNotFoundException;
15
import java.io.FileOutputStream;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.OutputStream;
19
import java.io.StringReader;
20
import java.nio.file.Path;
21
import java.nio.file.Paths;
22
import java.util.ArrayDeque;
23
import java.util.ArrayList;
24
import java.util.Collection;
25
import java.util.Deque;
26
import java.util.Enumeration;
27
import java.util.HashMap;
28
import java.util.Iterator;
29
import java.util.LinkedHashMap;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Properties;
33
import java.util.concurrent.locks.Lock;
34
import java.util.concurrent.locks.ReentrantLock;
35
import java.util.jar.JarEntry;
36
import java.util.jar.JarFile;
37
import java.util.regex.Pattern;
38

39
import javax.servlet.Filter;
40
import javax.servlet.ServletConfig;
41
import javax.servlet.ServletContext;
42
import javax.servlet.ServletException;
43
import javax.servlet.ServletRequest;
44
import javax.servlet.http.HttpServlet;
45
import javax.servlet.http.HttpServletRequest;
46
import javax.xml.parsers.DocumentBuilder;
47
import javax.xml.parsers.DocumentBuilderFactory;
48
import javax.xml.parsers.ParserConfigurationException;
49
import javax.xml.transform.Transformer;
50
import javax.xml.transform.TransformerException;
51
import javax.xml.transform.TransformerFactory;
52
import javax.xml.transform.dom.DOMSource;
53
import javax.xml.transform.stream.StreamResult;
54

55
import org.openmrs.api.APIException;
56
import org.openmrs.api.context.Context;
57
import org.openmrs.module.Module;
58
import org.openmrs.module.ModuleException;
59
import org.openmrs.module.ModuleFactory;
60
import org.openmrs.module.ModuleUtil;
61
import org.openmrs.module.web.filter.ModuleFilterConfig;
62
import org.openmrs.module.web.filter.ModuleFilterDefinition;
63
import org.openmrs.module.web.filter.ModuleFilterMapping;
64
import org.openmrs.scheduler.SchedulerException;
65
import org.openmrs.scheduler.SchedulerService;
66
import org.openmrs.scheduler.TaskDefinition;
67
import org.openmrs.util.OpenmrsUtil;
68
import org.openmrs.util.PrivilegeConstants;
69
import org.openmrs.web.DispatcherServlet;
70
import org.openmrs.web.StaticDispatcherServlet;
71
import org.slf4j.Logger;
72
import org.slf4j.LoggerFactory;
73
import org.springframework.web.context.support.WebApplicationContextUtils;
74
import org.springframework.web.context.support.XmlWebApplicationContext;
75
import org.w3c.dom.Document;
76
import org.w3c.dom.Element;
77
import org.w3c.dom.NamedNodeMap;
78
import org.w3c.dom.Node;
79
import org.w3c.dom.NodeList;
80
import org.xml.sax.InputSource;
81

82
public class WebModuleUtil {
83

84
        private WebModuleUtil() {
85
        }
86
        
87
        private static final Logger log = LoggerFactory.getLogger(WebModuleUtil.class);
1✔
88
        
89
        private static final Lock SERVLET_LOCK = new ReentrantLock();
1✔
90
        
91
        private static final Lock FILTERS_LOCK = new ReentrantLock();
1✔
92
        
93
        // caches all modules' mapped servlets
94
        private static final Map<String, HttpServlet> MODULE_SERVLETS = new HashMap<>();
1✔
95
        
96
        // caches all modules filters and filter-mappings
97
        private static final Map<Module, Collection<Filter>> MODULE_FILTERS = new HashMap<>();
1✔
98
        
99
        private static final Map<String, Filter> MODULE_FILTERS_BY_NAME = new HashMap<>();
1✔
100
        
101
        private static final Deque<ModuleFilterMapping> MODULE_FILTER_MAPPINGS = new ArrayDeque<>();
1✔
102
        
103
        private static DispatcherServlet dispatcherServlet = null;
1✔
104
        
105
        private static StaticDispatcherServlet staticDispatcherServlet = null;
1✔
106
        
107
        /**
108
         * Performs the webapp specific startup needs for modules Normal startup is done in
109
         * {@link ModuleFactory#startModule(Module)} If delayContextRefresh is true, the spring context
110
         * is not rerun. This will save a lot of time, but it also means that the calling method is
111
         * responsible for restarting the context if necessary (the calling method will also have to
112
         * call {@link #loadServlets(Module, ServletContext)} and
113
         * {@link #loadFilters(Module, ServletContext)}).<br>
114
         * <br>
115
         * If delayContextRefresh is true and this module should have caused a context refresh, a true
116
         * value is returned. Otherwise, false is returned
117
         *
118
         * @param mod Module to start
119
         * @param servletContext the current ServletContext
120
         * @param delayContextRefresh true/false whether or not to do the context refresh
121
         * @return boolean whether or not the spring context need to be refreshed
122
         */
123
        public static boolean startModule(Module mod, ServletContext servletContext, boolean delayContextRefresh) {
124
                
125
                log.debug("Trying to start module {}", mod);
1✔
126
                
127
                // only try and start this module if the api started it without a
128
                // problem.
129
                if (ModuleFactory.isModuleStarted(mod) && !mod.hasStartupError()) {
1✔
130
                        
131
                        String realPath = getRealPath(servletContext);
1✔
132
                        
133
                        if (realPath == null) {
1✔
134
                                realPath = System.getProperty("user.dir");
1✔
135
                        }
136
                        
137
                        File webInf = new File(realPath + "/WEB-INF".replace("/", File.separator));
1✔
138
                        if (!webInf.exists()) {
1✔
139
                                webInf.mkdir();
1✔
140
                        }
141
                        
142
                        // flag to tell whether we added any xml/dwr/etc changes that necessitate a refresh
143
                        // of the web application context
144
                        boolean moduleNeedsContextRefresh = false;
1✔
145
                        
146
                        // copy the html files into the webapp (from /web/module/ in the module)
147
                        // also looks for a spring context file. If found, schedules spring to be restarted
148
                        JarFile jarFile = null;
1✔
149
                        OutputStream outStream = null;
1✔
150
                        InputStream inStream = null;
1✔
151
                        try {
152
                                File modFile = mod.getFile();
1✔
153
                                jarFile = new JarFile(modFile);
×
154
                                Enumeration<JarEntry> entries = jarFile.entries();
×
155
                                
156
                                while (entries.hasMoreElements()) {
×
157
                                        JarEntry entry = entries.nextElement();
×
158
                                        String name = entry.getName();
×
159
                                        if (Paths.get(name).startsWith("..")) {
×
160
                                                throw new UnsupportedOperationException("Attempted to write file '" + name + "' rejected as it attempts to write outside the chosen directory. This may be the result of a zip-slip style attack.");
×
161
                                        }
162
                                        
163
                                        log.debug("Entry name: {}", name);
×
164
                                        if (name.startsWith("web/module/")) {
×
165
                                                // trim out the starting path of "web/module/"
166
                                                String filepath = name.substring(11);
×
167
                                                
168
                                                StringBuilder absPath = new StringBuilder(realPath + "/WEB-INF");
×
169
                                                
170
                                                // If this is within the tag file directory, copy it into /WEB-INF/tags/module/moduleId/...
171
                                                if (filepath.startsWith("tags/")) {
×
172
                                                        filepath = filepath.substring(5);
×
173
                                                        absPath.append("/tags/module/");
×
174
                                                }
175
                                                // Otherwise, copy it into /WEB-INF/view/module/moduleId/...
176
                                                else {
177
                                                        absPath.append("/view/module/");
×
178
                                                }
179
                                                
180
                                                // if a module id has a . in it, we should treat that as a /, i.e. files in the module
181
                                                // ui.springmvc should go in folder names like .../ui/springmvc/...
182
                                                absPath.append(mod.getModuleIdAsPath()).append("/").append(filepath);
×
183
                                                log.debug("Moving file from: {} to {}", name, absPath);
×
184
                                                
185
                                                // get the output file
186
                                                File outFile = new File(absPath.toString().replace("/", File.separator));
×
187
                                                if (entry.isDirectory()) {
×
188
                                                        if (!outFile.exists()) {
×
189
                                                                outFile.mkdirs();
×
190
                                                        }
191
                                                } else {
192
                                                        // make the parent directories in case it doesn't exist
193
                                                        File parentDir = outFile.getParentFile();
×
194
                                                        if (!parentDir.exists()) {
×
195
                                                                parentDir.mkdirs();
×
196
                                                        }
197
                                                        
198
                                                        // copy the contents over to the webapp for non directories
199
                                                        outStream = new FileOutputStream(outFile, false);
×
200
                                                        inStream = jarFile.getInputStream(entry);
×
201
                                                        OpenmrsUtil.copyFile(inStream, outStream);
×
202
                                                }
203
                                        } else if ("moduleApplicationContext.xml".equals(name) || "webModuleApplicationContext.xml".equals(name)) {
×
204
                                                moduleNeedsContextRefresh = true;
×
205
                                        } else if (name.equals(mod.getModuleId() + "Context.xml")) {
×
206
                                                String msg = "DEPRECATED: '" + name
×
207
                                                        + "' should be named 'moduleApplicationContext.xml' now. Please update/upgrade. ";
208
                                                throw new ModuleException(msg, mod.getModuleId());
×
209
                                        }
210
                                }
×
211
                        }
212
                        catch (IOException io) {
1✔
213
                                log.warn("Unable to copy files from module " + mod.getModuleId() + " to the web layer", io);
1✔
214
                        }
215
                        finally {
216
                                if (jarFile != null) {
1✔
217
                                        try {
218
                                                jarFile.close();
×
219
                                        }
220
                                        catch (IOException io) {
×
221
                                                log.warn("Couldn't close jar file: " + jarFile.getName(), io);
×
222
                                        }
×
223
                                }
224
                                if (inStream != null) {
1✔
225
                                        try {
226
                                                inStream.close();
×
227
                                        }
228
                                        catch (IOException io) {
×
229
                                                log.warn("Couldn't close InputStream: " + io);
×
230
                                        }
×
231
                                }
232
                                if (outStream != null) {
1✔
233
                                        try {
234
                                                outStream.close();
×
235
                                        }
236
                                        catch (IOException io) {
×
237
                                                log.warn("Couldn't close OutputStream: " + io);
×
238
                                        }
×
239
                                }
240
                        }
241
                        
242
                        // find and add the dwr code to the dwr-modules.xml file (if defined)
243
                        InputStream inputStream = null;
1✔
244
                        try {
245
                                Document config = mod.getConfig();
1✔
246
                                Element root = config.getDocumentElement();
1✔
247
                                if (root.getElementsByTagName("dwr").getLength() > 0) {
1✔
248
                                        
249
                                        // get the dwr-module.xml file that we're appending our code to
250
                                        File f = new File(realPath + "/WEB-INF/dwr-modules.xml".replace("/", File.separator));
1✔
251
                                        
252
                                        // testing if file exists
253
                                        if (!f.exists()) {
1✔
254
                                                // if it does not -> needs to be created
255
                                                createDwrModulesXml(realPath);
1✔
256
                                        }
257
                                        
258
                                        inputStream = new FileInputStream(f);
1✔
259
                                        Document dwrmodulexml = getDWRModuleXML(inputStream, realPath);
1✔
260
                                        Element outputRoot = dwrmodulexml.getDocumentElement();
1✔
261
                                        
262
                                        // loop over all of the children of the "dwr" tag
263
                                        Node node = root.getElementsByTagName("dwr").item(0);
1✔
264
                                        Node current = node.getFirstChild();
1✔
265
                                        
266
                                        while (current != null) {
1✔
267
                                                if ("allow".equals(current.getNodeName()) || "signatures".equals(current.getNodeName())
1✔
268
                                                        || "init".equals(current.getNodeName())) {
1✔
269
                                                        ((Element) current).setAttribute("moduleId", mod.getModuleId());
1✔
270
                                                        outputRoot.appendChild(dwrmodulexml.importNode(current, true));
1✔
271
                                                }
272
                                                
273
                                                current = current.getNextSibling();
1✔
274
                                        }
275
                                        
276
                                        moduleNeedsContextRefresh = true;
1✔
277
                                        
278
                                        // save the dwr-modules.xml file.
279
                                        OpenmrsUtil.saveDocument(dwrmodulexml, f);
1✔
280
                                }
281
                        }
282
                        catch (FileNotFoundException e) {
×
283
                                throw new ModuleException(realPath + "/WEB-INF/dwr-modules.xml file doesn't exist.", e);
×
284
                        }
285
                        finally {
286
                                if (inputStream != null) {
1✔
287
                                        try {
288
                                                inputStream.close();
1✔
289
                                        }
290
                                        catch (IOException io) {
×
291
                                                log.error("Error while closing input stream", io);
×
292
                                        }
1✔
293
                                }
294
                        }
295
                        
296
                        // mark to delete the entire module web directory on exit
297
                        // this will usually only be used when an improper shutdown has occurred.
298
                        String folderPath = realPath + "/WEB-INF/view/module/" + mod.getModuleIdAsPath();
1✔
299
                        File outFile = new File(folderPath.replace("/", File.separator));
1✔
300
                        outFile.deleteOnExit();
1✔
301
                        
302
                        // additional checks on module needing a context refresh
303
                        if (!moduleNeedsContextRefresh && mod.getAdvicePoints() != null && !mod.getAdvicePoints().isEmpty()) {
1✔
304
                                
305
                                // AOP advice points are only loaded during the context refresh now.
306
                                // if the context hasn't been marked to be refreshed yet, mark it
307
                                // now if this module defines some advice
308
                                moduleNeedsContextRefresh = true;
×
309
                                
310
                        }
311
                        
312
                        // refresh the spring web context to get the just-created xml
313
                        // files into it (if we copied an xml file)
314
                        if (moduleNeedsContextRefresh && !delayContextRefresh) {
1✔
NEW
315
                                log.debug("Refreshing context for module {}", mod.getModuleId());
×
316
                                
317
                                try {
318
                                        refreshWAC(servletContext, false, mod);
×
NEW
319
                                        log.debug("Done refreshing context for module {}", mod.getModuleId());
×
320
                                }
321
                                catch (Exception e) {
×
322
                                        String msg = "Unable to refresh the WebApplicationContext";
×
323
                                        mod.setStartupErrorMessage(msg, e);
×
324
                                        
325
                                        if (log.isWarnEnabled()) {
×
326
                                                log.warn(msg + " for module: " + mod.getModuleId(), e);
×
327
                                        }
328
                                        
329
                                        try {
330
                                                stopModule(mod, servletContext, true);
×
331
                                                ModuleFactory.stopModule(mod, true, true); //remove jar from classloader play
×
332
                                        }
333
                                        catch (Exception e2) {
×
334
                                                // exception expected with most modules here
335
                                                if (log.isWarnEnabled()) {
×
336
                                                        log.warn("Error while stopping a module that had an error on refreshWAC", e2);
×
337
                                                }
338
                                        }
×
339
                                        
340
                                        // try starting the application context again
NEW
341
                                        log.debug("Refreshing context for module {} (re-trying)", mod.getModuleId());
×
UNCOV
342
                                        refreshWAC(servletContext, false, mod);
×
NEW
343
                                        log.debug("Done refreshing context for module {} (re-trying)", mod.getModuleId());
×
344
                                        
345
                                        notifySuperUsersAboutModuleFailure(mod);
×
346
                                }
×
347
                                
348
                        }
349
                        
350
                        if (!delayContextRefresh && ModuleFactory.isModuleStarted(mod)) {
1✔
351
                                // only loading the servlets/filters if spring is refreshed because one
352
                                // might depend on files being available in spring
353
                                // if the caller wanted to delay the refresh then they are responsible for
354
                                // calling these two methods on the module
355
                                
356
                                // find and cache the module's servlets
357
                                //(only if the module started successfully previously)
358
                                log.debug("Loading servlets and filters for module {}", mod);
×
359
                                servletContext.setAttribute(OpenmrsJspServlet.OPENMRS_TLD_SCAN_NEEDED, true);
×
360
                                loadServlets(mod, servletContext);
×
361
                                loadFilters(mod, servletContext);
×
362
                        }
363
                        
364
                        // return true if the module needs a context refresh and we didn't do it here
365
                        return (moduleNeedsContextRefresh && delayContextRefresh);
1✔
366
                        
367
                }
368
                
369
                // we aren't processing this module, so a context refresh is not necessary
370
                return false;
×
371
        }
372
        
373
        /** Stops all tasks started by given module
374
         * @param mod
375
         */
376
        private static void stopTasks(Module mod) {
377
                SchedulerService schedulerService;
378
                try {
379
                        schedulerService = Context.getSchedulerService();
×
380
                } catch (NullPointerException | APIException e) {
×
381
                        // if we got here, the scheduler has already been shut down, so there's no work to do
382
                        return;
×
383
                }
×
384
                
385
                String modulePackageName = mod.getPackageName();
×
386
                for (TaskDefinition task : schedulerService.getRegisteredTasks()) {
×
387
                        
388
                        String taskClass = task.getTaskClass();
×
389
                        if (isModulePackageNameInTaskClass(modulePackageName, taskClass)) {
×
390
                                try {
391
                                        schedulerService.shutdownTask(task);
×
392
                                }
393
                                catch (SchedulerException e) {
×
394
                                        log.error("Couldn't stop task:" + task + " for module: " + mod);
×
395
                                }
×
396
                        }
397
                }
×
398
        }
×
399
        
400
        /**
401
         * Checks if module package name is in task class name
402
         * @param modulePackageName the package name of module
403
         * @param taskClass the class of given task
404
         * @return true if task and module are in the same package
405
         * <strong>Should</strong> return false for different package names
406
         * <strong>Should</strong> return false if module has longer package name
407
         * <strong>Should</strong> properly match subpackages
408
         * <strong>Should</strong> return false for empty package names
409
         */
410
        public static boolean isModulePackageNameInTaskClass(String modulePackageName, String taskClass) {
411
                return modulePackageName.length() <= taskClass.length()
1✔
412
                        && taskClass.matches(Pattern.quote(modulePackageName) + "(\\..*)+");
1✔
413
        }
414
        
415
        /**
416
         * Send an Alert to all super users that the given module did not start successfully.
417
         *
418
         * @param mod The Module that failed
419
         */
420
        private static void notifySuperUsersAboutModuleFailure(Module mod) {
421
                try {
422
                        // Add the privileges necessary for notifySuperUsers
423
                        Context.addProxyPrivilege(PrivilegeConstants.MANAGE_ALERTS);
×
424
                        Context.addProxyPrivilege(PrivilegeConstants.GET_USERS);
×
425
                        
426
                        // Send an alert to all administrators
427
                        Context.getAlertService().notifySuperUsers("Module.startupError.notification.message", null, mod.getName());
×
428
                }
429
                finally {
430
                        // Remove added privileges
431
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_USERS);
×
432
                        Context.removeProxyPrivilege(PrivilegeConstants.MANAGE_ALERTS);
×
433
                }
434
        }
×
435
        
436
        /**
437
         * This method will find and cache this module's servlets (so that it doesn't have to look them
438
         * up every time)
439
         *
440
         * @param mod
441
         * @param servletContext the servlet context
442
         */
443
        public static void loadServlets(Module mod, ServletContext servletContext) {
444
                Element rootNode = mod.getConfig().getDocumentElement();
1✔
445
                NodeList servletTags = rootNode.getElementsByTagName("servlet");
1✔
446
                
447
                for (int i = 0; i < servletTags.getLength(); i++) {
1✔
448
                        Node node = servletTags.item(i);
1✔
449
                        NodeList childNodes = node.getChildNodes();
1✔
450
                        String name = "", className = "";
1✔
451

452
                        Map<String, String> initParams = new HashMap<>();
1✔
453
                        for (int j = 0; j < childNodes.getLength(); j++) {
1✔
454
                                Node childNode = childNodes.item(j);
1✔
455
                                if ("servlet-name".equals(childNode.getNodeName())) {
1✔
456
                                        if (childNode.getTextContent() != null) {
1✔
457
                                                name = childNode.getTextContent().trim();
1✔
458
                                        }
459
                                } else if ("servlet-class".equals(childNode.getNodeName()) && childNode.getTextContent() != null) {
1✔
460
                                        className = childNode.getTextContent().trim();
1✔
461
                                } else if ("init-param".equals(childNode.getNodeName())) {
1✔
462
                                        NodeList initParamChildren = childNode.getChildNodes();
1✔
463
                                        String paramName = null, paramValue = null;
1✔
464
                                        for (int k = 0; k < initParamChildren.getLength(); k++) {
1✔
465
                                                Node initParamChild = initParamChildren.item(k);
1✔
466
                                                if ("param-name".equals(initParamChild.getNodeName()) && initParamChild.getTextContent() != null) {
1✔
467
                                                        paramName = initParamChild.getTextContent().trim();
1✔
468
                                                } else if ("param-value".equals(initParamChild.getNodeName()) && initParamChild.getTextContent() != null) {
1✔
469
                                                        paramValue = initParamChild.getTextContent().trim();
1✔
470
                                                }
471
                                        }
472

473
                                        if (paramName != null && paramValue != null) {
1✔
474
                                                initParams.put(paramName, paramValue);
1✔
475
                                        }
476
                                }
477
                        }
478
                        if (name.length() == 0 || className.length() == 0) {
1✔
479
                                log.warn("both 'servlet-name' and 'servlet-class' are required for the 'servlet' tag. Given '" + name
×
480
                                        + "' and '" + className + "' for module " + mod.getName());
×
481
                                continue;
×
482
                        }
483
                        
484
                        HttpServlet httpServlet;
485
                        try {
486
                                httpServlet = (HttpServlet) ModuleFactory.getModuleClassLoader(mod).loadClass(className).newInstance();
1✔
487
                        }
488
                        catch (ClassCastException e) {
×
489
                                log.warn("Class {} from module {} is not a valid HttpServlet", className, mod, e);
×
490
                                continue;
×
491
                        }
492
                        catch (ClassNotFoundException e) {
×
493
                                log.warn("Class {} not found for servlet {} from module {}", className, name, mod, e);
×
494
                                continue;
×
495
                        }
496
                        catch (IllegalAccessException e) {
×
497
                                log.warn("Class {} cannot be accessed for servlet {} from module {}", className, name, mod, e);
×
498
                                continue;
×
499
                        }
500
                        catch (InstantiationException e) {
×
501
                                log.warn("Class {} cannot be instantiated for servlet {} from module {}", className, name, mod, e);
×
502
                                continue;
×
503
                        }
1✔
504
                        
505
                        try {
506
                                log.debug("Initializing {} servlet. - {}.", name, httpServlet);
1✔
507
                                ServletConfig servletConfig = new ModuleServlet.SimpleServletConfig(name, servletContext, initParams);
1✔
508
                                httpServlet.init(servletConfig);
1✔
509
                        }
510
                        catch (Exception e) {
×
511
                                log.warn("Unable to initialize servlet {}", name, e);
×
512
                                throw new ModuleException("Unable to initialize servlet " + name, mod.getModuleId(), e);
×
513
                        }
1✔
514
                        
515
                        // don't allow modules to overwrite servlets of other modules.
516
                        HttpServlet otherServletUsingSameName = MODULE_SERVLETS.get(name);
1✔
517
                        if (otherServletUsingSameName != null) {
1✔
518
                                String otherServletName = otherServletUsingSameName.getClass().getName();
×
519
                                throw new ModuleException("A servlet mapping with name " + name + " is already in use and pointing at: "
×
520
                                        + otherServletName + " from another installed module and this module is trying"
521
                                        + " to use that same name.  Either the module attempting to be installed (" + mod
522
                                        + ") will not work or the other one will not.  Please consult the developers of these two"
523
                                        + " modules to sort this out.");
524
                        }
525
                        
526
                        log.debug("Caching the {} servlet.", name);
1✔
527
                        
528
                        SERVLET_LOCK.lock();
1✔
529
                        try {
530
                                MODULE_SERVLETS.put(name, httpServlet);
1✔
531
                        } finally {
532
                                SERVLET_LOCK.unlock();
1✔
533
                        }
534
                }
535
        }
1✔
536
        
537
        /**
538
         * Remove the servlets defined for this module
539
         *
540
         * @param mod the module that is being stopped that needs its servlets removed
541
         */
542
        public static void unloadServlets(Module mod) {
543
                Element rootNode = mod.getConfig().getDocumentElement();
1✔
544
                NodeList servletTags = rootNode.getElementsByTagName("servlet");
1✔
545
                
546
                for (int i = 0; i < servletTags.getLength(); i++) {
1✔
547
                        Node node = servletTags.item(i);
1✔
548
                        NodeList childNodes = node.getChildNodes();
1✔
549
                        String name;
550
                        for (int j = 0; j < childNodes.getLength(); j++) {
1✔
551
                                Node childNode = childNodes.item(j);
1✔
552
                                if ("servlet-name".equals(childNode.getNodeName()) && childNode.getTextContent() != null) {
1✔
553
                                        name = childNode.getTextContent().trim();
1✔
554
                                        
555
                                        HttpServlet servlet;
556
                                        SERVLET_LOCK.lock();
1✔
557
                                        try {
558
                                                servlet = MODULE_SERVLETS.get(name);
1✔
559
                                        } finally {
560
                                                SERVLET_LOCK.unlock();
1✔
561
                                        }
562
                                        
563
                                        if (servlet != null) {
1✔
564
                                                // shut down the servlet
565
                                                servlet.destroy();
1✔
566
                                        }
567
                                        
568
                                        SERVLET_LOCK.lock();
1✔
569
                                        try {
570
                                                MODULE_SERVLETS.remove(name);
1✔
571
                                        } finally {
572
                                                SERVLET_LOCK.unlock();
1✔
573
                                        }
574
                                }
575
                        }
576
                }
577
        }
1✔
578
        
579
        /**
580
         * This method will initialize and store this module's filters
581
         *
582
         * @param module - The Module to load and register Filters
583
         * @param servletContext - The servletContext within which this method is called
584
         */
585
        public static void loadFilters(Module module, ServletContext servletContext) {
586
                
587
                // Load Filters
588
                Map<String, Filter> filters = new LinkedHashMap<>();
×
589
                
590
                Map<String, Filter> existingFilters;
591
                FILTERS_LOCK.lock();
×
592
                try {
593
                        existingFilters = new HashMap<>(MODULE_FILTERS_BY_NAME);
×
594
                } finally {
595
                        FILTERS_LOCK.unlock();
×
596
                }
597
                
598
                for (ModuleFilterDefinition def : ModuleFilterDefinition.retrieveFilterDefinitions(module)) {
×
599
                        String name = def.getFilterName();
×
600
                        String className = def.getFilterClass();
×
601
                        
602
                        if (existingFilters.containsKey(name)) {
×
603
                                throw new ModuleException("A filter with the name " + name + " is already in use and pointing at: "
×
604
                                        + existingFilters.get(name).getClass().getName()
×
605
                                        + " from another installed module and this module is trying"
606
                                        + " to use that same name.  Either the module attempting to be installed (" + module
607
                                        + ") will not work or the other one will not.  Please consult the developers of these two"
608
                                        + " modules to sort this out.");
609
                        }
610
                        
611
                        ModuleFilterConfig config = ModuleFilterConfig.getInstance(def, servletContext);
×
612
                        
613
                        Filter filter;
614
                        try {
615
                                filter = (Filter) ModuleFactory.getModuleClassLoader(module).loadClass(className).newInstance();
×
616
                        }
617
                        catch (ClassCastException e) {
×
618
                                log.warn("Class {} from module {} is not a valid Filter", className, module, e);
×
619
                                continue;
×
620
                        }
621
                        catch (ClassNotFoundException e) {
×
622
                                log.warn("Class {} not found for servlet {} from module {}", className, name, module, e);
×
623
                                continue;
×
624
                        }
625
                        catch (IllegalAccessException e) {
×
626
                                log.warn("Class {} cannot be accessed for servlet {} from module {}", className, name, module, e);
×
627
                                continue;
×
628
                        }
629
                        catch (InstantiationException e) {
×
630
                                log.warn("Class {} cannot be instantiated for servlet {} from module {}", className, name, module, e);
×
631
                                continue;
×
632
                        }
×
633
                        
634
                        try {
635
                                log.debug("Initializing {} filter. - {}.", name, filter);
×
636
                                filter.init(config);
×
637
                        }
638
                        catch (Exception e) {
×
639
                                log.warn("Unable to initialize servlet {}", name, e);
×
640
                                throw new ModuleException("Unable to initialize servlet " + name, module.getModuleId(), e);
×
641
                        }
×
642
                        
643
                        filters.put(name, filter);
×
644
                }
×
645

646
                FILTERS_LOCK.lock();
×
647
                try {
648
                        MODULE_FILTERS.put(module, filters.values());
×
649
                        MODULE_FILTERS_BY_NAME.putAll(filters);
×
650
                        log.debug("Module {} successfully loaded {} filters.", module, filters.size());
×
651
                        
652
                        // Load Filter Mappings
653
                        Deque<ModuleFilterMapping> modMappings = ModuleFilterMapping.retrieveFilterMappings(module);
×
654
                        
655
                        // IMPORTANT: Filter load order
656
                        // retrieveFilterMappings will return the list of filters in the order they occur in the config.xml file
657
                        // here we add them to the *front* of the filter mappings
658
                        modMappings.descendingIterator().forEachRemaining(MODULE_FILTER_MAPPINGS::addFirst);
×
659
                        
660
                        log.debug("Module {} successfully loaded {} filter mappings.", module, modMappings.size());
×
661
                } finally {
662
                        FILTERS_LOCK.unlock();
×
663
                }
664
        }
×
665
        
666
        /**
667
         * This method will destroy and remove all filters that were registered by the passed
668
         * {@link Module}
669
         *
670
         * @param module - The Module for which you want to remove and destroy filters.
671
         */
672
        public static void unloadFilters(Module module) {
673
                
674
                // Unload Filter Mappings
675
                for (Iterator<ModuleFilterMapping> mapIter = MODULE_FILTER_MAPPINGS.iterator(); mapIter.hasNext();) {
×
676
                        ModuleFilterMapping mapping = mapIter.next();
×
677
                        if (module.equals(mapping.getModule())) {
×
678
                                mapIter.remove();
×
679
                                log.debug("Removed ModuleFilterMapping: " + mapping);
×
680
                        }
681
                }
×
682
                
683
                // unload Filters
684
                Collection<Filter> filters = MODULE_FILTERS.get(module);
×
685
                if (filters != null) {
×
686
                        try {
687
                                for (Filter f : filters) {
×
688
                                        f.destroy();
×
689
                                }
×
690
                        }
691
                        catch (Exception e) {
×
692
                                log.warn("An error occurred while trying to destroy and remove module Filter.", e);
×
693
                        }
×
694
                        
695
                        log.debug("Module: " + module.getModuleId() + " successfully unloaded " + filters.size() + " filters.");
×
696
                        MODULE_FILTERS.remove(module);
×
697

698
                        MODULE_FILTERS_BY_NAME.values().removeIf(filters::contains);
×
699
                }
700
        }
×
701
        
702
        /**
703
         * This method will return all Filters that have been registered a module
704
         *
705
         * @return A Collection of {@link Filter}s that have been registered by a module
706
         */
707
        public static Collection<Filter> getFilters() {
708
                return MODULE_FILTERS_BY_NAME.values();
×
709
        }
710
        
711
        /**
712
         * This method will return all Filter Mappings that have been registered by a module
713
         *
714
         * @return A Collection of all {@link ModuleFilterMapping}s that have been registered by a
715
         *         Module
716
         */
717
        public static Collection<ModuleFilterMapping> getFilterMappings() {
718
                return new ArrayList<>(MODULE_FILTER_MAPPINGS);
×
719
        }
720
        
721
        /**
722
         * Return List of Filters that have been loaded through Modules that have mappings that pass for
723
         * the passed request
724
         *
725
         * @param request - The request to check for matching {@link Filter}s
726
         * @return List of all {@link Filter}s that have filter mappings that match the passed request
727
         */
728
        public static List<Filter> getFiltersForRequest(ServletRequest request) {
729
                
730
                List<Filter> filters = new ArrayList<>();
×
731
                if (request != null) {
×
732
                        HttpServletRequest httpRequest = (HttpServletRequest) request;
×
733
                        String requestPath = httpRequest.getRequestURI();
×
734
                        
735
                        if (requestPath != null) {
×
736
                                if (requestPath.startsWith(httpRequest.getContextPath())) {
×
737
                                        requestPath = requestPath.substring(httpRequest.getContextPath().length());
×
738
                                }
739
                                for (ModuleFilterMapping filterMapping : WebModuleUtil.getFilterMappings()) {
×
740
                                        if (ModuleFilterMapping.filterMappingPasses(filterMapping, requestPath)) {
×
741
                                                Filter passedFilter = MODULE_FILTERS_BY_NAME.get(filterMapping.getFilterName());
×
742
                                                if (passedFilter != null) {
×
743
                                                        filters.add(passedFilter);
×
744
                                                } else {
745
                                                        log.warn("Unable to retrieve filter that has a name of " + filterMapping.getFilterName()
×
746
                                                                + " in filter mapping.");
747
                                                }
748
                                        }
749
                                }
×
750
                        }
751
                }
752
                return filters;
×
753
        }
754
        
755
        /**
756
         * @param inputStream
757
         * @param realPath
758
         * @return
759
         */
760
        private static Document getDWRModuleXML(InputStream inputStream, String realPath) {
761
                Document dwrmodulexml;
762
                try {
763
                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1✔
764
                        DocumentBuilder db = dbf.newDocumentBuilder();
1✔
765

766
                        // When asked to resolve external entities (such as a DTD) we return an InputSource
767
                        // with no data at the end, causing the parser to ignore the DTD.
768
                        db.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
1✔
769
                        dwrmodulexml = db.parse(inputStream);
1✔
770
                }
771
                catch (Exception e) {
×
772
                        throw new ModuleException("Error parsing dwr-modules.xml file", e);
×
773
                }
1✔
774
                
775
                return dwrmodulexml;
1✔
776
        }
777
        
778
        /**
779
         * Reverses all activities done by startModule(org.openmrs.module.Module) Normal stop/shutdown
780
         * is done by ModuleFactory
781
         */
782
        public static void shutdownModules(ServletContext servletContext) {
783
                
784
                String realPath = getRealPath(servletContext);
×
785
                
786
                // clear the module messages
787
                String messagesPath = realPath + "/WEB-INF/";
×
788
                File folder = new File(messagesPath.replace("/", File.separator));
×
789
                
790
                File[] files = folder.listFiles();
×
791
                if (folder.exists() && files != null) {
×
792
                        Properties emptyProperties = new Properties();
×
793
                        for (File f : files) {
×
794
                                if (f.getName().startsWith("module_messages")) {
×
795
                                        OpenmrsUtil.storeProperties(emptyProperties, f, "");
×
796
                                }
797
                        }
798
                }
799
                
800
                // call web shutdown for each module
801
                for (Module mod : ModuleFactory.getLoadedModules()) {
×
802
                        stopModule(mod, servletContext, true);
×
803
                }
×
804
                
805
        }
×
806
        
807
        /**
808
         * Reverses all visible activities done by startModule(org.openmrs.module.Module)
809
         *
810
         * @param mod
811
         * @param servletContext
812
         */
813
        public static void stopModule(Module mod, ServletContext servletContext) {
814
                stopModule(mod, servletContext, false);
×
815
        }
×
816
        
817
        /**
818
         * Reverses all visible activities done by startModule(org.openmrs.module.Module)
819
         *
820
         * @param mod
821
         * @param servletContext
822
         * @param skipRefresh
823
         */
824
        public static void stopModule(Module mod, ServletContext servletContext, boolean skipRefresh) {
825
                
826
                String moduleId = mod.getModuleId();
×
827
                String modulePackage = mod.getPackageName();
×
828
                
829
                // stop all dependent modules
830
                for (Module dependentModule : ModuleFactory.getStartedModules()) {
×
831
                        if (!dependentModule.equals(mod) && dependentModule.getRequiredModules().contains(modulePackage)) {
×
832
                                stopModule(dependentModule, servletContext, skipRefresh);
×
833
                        }
834
                }
×
835
                
836
                String realPath = getRealPath(servletContext);
×
837
                
838
                // delete the web files from the webapp
839
                String absPath = realPath + "/WEB-INF/view/module/" + moduleId;
×
840
                File moduleWebFolder = new File(absPath.replace("/", File.separator));
×
841
                if (moduleWebFolder.exists()) {
×
842
                        try {
843
                                OpenmrsUtil.deleteDirectory(moduleWebFolder);
×
844
                        }
845
                        catch (IOException io) {
×
846
                                log.warn("Couldn't delete: " + moduleWebFolder.getAbsolutePath(), io);
×
847
                        }
×
848
                }
849
                
850
                // (not) deleting module message properties
851
                
852
                // remove the module's servlets
853
                unloadServlets(mod);
×
854
                
855
                // remove the module's filters and filter mappings
856
                unloadFilters(mod);
×
857
                
858
                // stop all tasks associated with mod
859
                stopTasks(mod);
×
860
                
861
                // remove this module's entries in the dwr xml file
862
                InputStream inputStream = null;
×
863
                try {
864
                        Document config = mod.getConfig();
×
865
                        Element root = config.getDocumentElement();
×
866
                        // if they defined any xml element
867
                        if (root.getElementsByTagName("dwr").getLength() > 0) {
×
868
                                
869
                                // get the dwr-module.xml file that we're appending our code to
870
                                File f = new File(realPath + "/WEB-INF/dwr-modules.xml".replace("/", File.separator));
×
871
                                
872
                                // testing if file exists
873
                                if (!f.exists()) {
×
874
                                        // if it does not -> needs to be created
875
                                        createDwrModulesXml(realPath);
×
876
                                }
877
                                
878
                                inputStream = new FileInputStream(f);
×
879
                                Document dwrmodulexml = getDWRModuleXML(inputStream, realPath);
×
880
                                Element outputRoot = dwrmodulexml.getDocumentElement();
×
881
                                
882
                                // loop over all of the children of the "dwr" tag
883
                                // and remove all "allow" and "signature" tags that have the
884
                                // same moduleId attr as the module being stopped
885
                                NodeList nodeList = outputRoot.getChildNodes();
×
886
                                int i = 0;
×
887
                                while (i < nodeList.getLength()) {
×
888
                                        Node current = nodeList.item(i);
×
889
                                        if ("allow".equals(current.getNodeName()) || "signatures".equals(current.getNodeName())) {
×
890
                                                NamedNodeMap attrs = current.getAttributes();
×
891
                                                Node attr = attrs.getNamedItem("moduleId");
×
892
                                                if (attr != null && moduleId.equals(attr.getNodeValue())) {
×
893
                                                        outputRoot.removeChild(current);
×
894
                                                } else {
895
                                                        i++;
×
896
                                                }
897
                                        } else {
×
898
                                                i++;
×
899
                                        }
900
                                }
×
901
                                
902
                                // save the dwr-modules.xml file.
903
                                OpenmrsUtil.saveDocument(dwrmodulexml, f);
×
904
                        }
905
                }
906
                catch (FileNotFoundException e) {
×
907
                        throw new ModuleException(realPath + "/WEB-INF/dwr-modules.xml file doesn't exist.", e);
×
908
                }
909
                finally {
910
                        if (inputStream != null) {
×
911
                                try {
912
                                        inputStream.close();
×
913
                                }
914
                                catch (IOException io) {
×
915
                                        log.error("Error while closing input stream", io);
×
916
                                }
×
917
                        }
918
                }
919
                
920
                if (!skipRefresh) {        
×
921
                        refreshWAC(servletContext, false, null);
×
922
                }
923
                
924
        }
×
925
        
926
        /**
927
         * Stops, closes, and refreshes the Spring context for the given <code>servletContext</code>
928
         *
929
         * @param servletContext
930
         * @param isOpenmrsStartup if this refresh is being done at application startup
931
         * @param startedModule the module that was just started and waiting on the context refresh
932
         * @return The newly refreshed webApplicationContext
933
         */
934
        public static XmlWebApplicationContext refreshWAC(ServletContext servletContext, boolean isOpenmrsStartup,
935
                Module startedModule) {
936
                XmlWebApplicationContext wac = (XmlWebApplicationContext) WebApplicationContextUtils
×
937
                        .getWebApplicationContext(servletContext);
×
NEW
938
                log.debug("Refreshing Web Application Context of class: {}", wac.getClass().getName());
×
939
                
940
                if (dispatcherServlet != null) {
×
941
                        dispatcherServlet.stopAndCloseApplicationContext();
×
942
                }
943
                
944
                if (staticDispatcherServlet != null) {
×
945
                        staticDispatcherServlet.stopAndCloseApplicationContext();
×
946
                }
947
                
948
                XmlWebApplicationContext newAppContext = (XmlWebApplicationContext) ModuleUtil.refreshApplicationContext(wac,
×
949
                    isOpenmrsStartup, startedModule);
950
                
951
                try {
952
                        // must "refresh" the spring dispatcherservlet as well to add in
953
                        //the new handlerMappings
954
                        if (dispatcherServlet != null) {
×
955
                                dispatcherServlet.reInitFrameworkServlet();
×
956
                        }
957
                        
958
                        if (staticDispatcherServlet != null) {
×
959
                                staticDispatcherServlet.refreshApplicationContext();
×
960
                        }
961
                }
962
                catch (ServletException se) {
×
963
                        log.warn("Caught a servlet exception while refreshing the dispatcher servlet", se);
×
964
                }
×
965
                
966
                return newAppContext;
×
967
        }
968
        
969
        /**
970
         * Save the dispatcher servlet for use later (reinitializing things)
971
         *
972
         * @param ds
973
         */
974
        public static void setDispatcherServlet(DispatcherServlet ds) {
975
                log.debug("Setting dispatcher servlet: " + ds);
1✔
976
                dispatcherServlet = ds;
1✔
977
        }
1✔
978
        
979
        /**
980
         * Save the static content dispatcher servlet for use later when refreshing spring
981
         *
982
         * @param ds
983
         */
984
        public static void setStaticDispatcherServlet(StaticDispatcherServlet ds) {
985
                log.debug("Setting dispatcher servlet for static content: " + ds);
×
986
                staticDispatcherServlet = ds;
×
987
        }
×
988
        
989
        /**
990
         * Finds the servlet defined by the servlet name
991
         *
992
         * @param servletName the name of the servlet out of the path
993
         * @return the current servlet or null if none defined
994
         */
995
        public static HttpServlet getServlet(String servletName) {
996
                return MODULE_SERVLETS.get(servletName);
1✔
997
        }
998
        
999
        /**
1000
         * Retrieves a path to a folder that stores web files of a module. <br>
1001
         * (path-to-openmrs/WEB-INF/view/module/moduleid)
1002
         *
1003
         * @param moduleId module id (e.g., "basicmodule")
1004
         * @return a path to a folder that stores web files or null if not in a web environment
1005
         * <strong>Should</strong> return the correct module folder
1006
         * <strong>Should</strong> return null if the dispatcher servlet is not yet set
1007
         * <strong>Should</strong> return the correct module folder if real path has a trailing slash
1008
         */
1009
        public static String getModuleWebFolder(String moduleId) {
1010
                if (dispatcherServlet == null) {
1✔
1011
                        throw new ModuleException("Dispatcher servlet must be present in the web environment");
1✔
1012
                }
1013
                
1014
                String moduleFolder = "WEB-INF/view/module/";
1✔
1015
                String realPath = dispatcherServlet.getServletContext().getRealPath("");
1✔
1016
                String moduleWebFolder;
1017
                
1018
                //RealPath may contain '/' on Windows when running tests with the mocked servlet context
1019
                if (realPath.endsWith(File.separator) || realPath.endsWith("/")) {
1✔
1020
                        moduleWebFolder = realPath + moduleFolder;
1✔
1021
                } else {
1022
                        moduleWebFolder = realPath + "/" + moduleFolder;
1✔
1023
                }
1024
                
1025
                moduleWebFolder += moduleId;
1✔
1026
                
1027
                return moduleWebFolder.replace("/", File.separator);
1✔
1028
        }
1029
        
1030
        public static void createDwrModulesXml(String realPath) {
1031
                
1032
                try {
1033
                        
1034
                        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
1✔
1035
                        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
1✔
1036
                        
1037
                        // root elements
1038
                        Document doc = docBuilder.newDocument();
1✔
1039
                        Element rootElement = doc.createElement("dwr");
1✔
1040
                        doc.appendChild(rootElement);
1✔
1041
                        
1042
                        // write the content into xml file
1043
                        TransformerFactory transformerFactory = TransformerFactory.newInstance();
1✔
1044
                        Transformer transformer = transformerFactory.newTransformer();
1✔
1045
                        DOMSource source = new DOMSource(doc);
1✔
1046
                        StreamResult result = new StreamResult(new File(realPath
1✔
1047
                                + "/WEB-INF/dwr-modules.xml".replace("/", File.separator)));
1✔
1048
                        
1049
                        transformer.transform(source, result);
1✔
1050
                        
1051
                }
1052
                catch (ParserConfigurationException pce) {
×
1053
                        log.error("Failed to parse document", pce);
×
1054
                }
1055
                catch (TransformerException tfe) {
×
1056
                        log.error("Failed to transorm xml source", tfe);
×
1057
                }
1✔
1058
        }
1✔
1059

1060
        public static String getRealPath(ServletContext servletContext) {
1061
                return servletContext.getRealPath("");
1✔
1062
        }
1063
        
1064
}
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