• 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

57.59
/api/src/main/java/org/openmrs/module/ModuleUtil.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;
11

12
import java.io.ByteArrayInputStream;
13
import java.io.ByteArrayOutputStream;
14
import java.io.File;
15
import java.io.FileOutputStream;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.net.HttpURLConnection;
19
import java.net.MalformedURLException;
20
import java.net.URL;
21
import java.net.URLConnection;
22
import java.nio.charset.StandardCharsets;
23
import java.util.ArrayList;
24
import java.util.Collection;
25
import java.util.Collections;
26
import java.util.Enumeration;
27
import java.util.HashMap;
28
import java.util.HashSet;
29
import java.util.LinkedHashSet;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Properties;
33
import java.util.Set;
34
import java.util.jar.JarEntry;
35
import java.util.jar.JarFile;
36
import java.util.regex.Matcher;
37
import java.util.regex.Pattern;
38
import java.util.zip.ZipEntry;
39

40
import org.apache.commons.io.IOUtils;
41
import org.apache.commons.lang3.StringUtils;
42
import org.apache.commons.lang3.math.NumberUtils;
43
import org.openmrs.GlobalProperty;
44
import org.openmrs.api.AdministrationService;
45
import org.openmrs.api.context.Context;
46
import org.openmrs.api.context.ServiceContext;
47
import org.openmrs.scheduler.SchedulerUtil;
48
import org.openmrs.util.OpenmrsClassLoader;
49
import org.openmrs.util.OpenmrsConstants;
50
import org.openmrs.util.OpenmrsUtil;
51
import org.slf4j.Logger;
52
import org.slf4j.LoggerFactory;
53
import org.springframework.context.support.AbstractRefreshableApplicationContext;
54

55
/**
56
 * Utility methods for working and manipulating modules
57
 */
58
public class ModuleUtil {
59

60
        private ModuleUtil() {
61
        }
62
        
63
        private static final Logger log = LoggerFactory.getLogger(ModuleUtil.class);
1✔
64
        
65
        /**
66
         * Start up the module system with the given properties.
67
         *
68
         * @param props Properties (OpenMRS runtime properties)
69
         */
70
        public static void startup(Properties props) throws ModuleMustStartException {
71
                
72
                String moduleListString = props.getProperty(ModuleConstants.RUNTIMEPROPERTY_MODULE_LIST_TO_LOAD);
1✔
73
                
74
                if (moduleListString == null || moduleListString.isEmpty()) {
1✔
75
                        // Attempt to get all of the modules from the modules folder
76
                        // and store them in the modules list
77
                        log.debug("Starting all modules");
×
78
                        ModuleFactory.loadModules();
×
79
                } else {
80
                        // use the list of modules and load only those
81
                        log.debug("Starting all modules in this list: " + moduleListString);
1✔
82
                        
83
                        String[] moduleArray = moduleListString.split(" ");
1✔
84
                        List<File> modulesToLoad = new ArrayList<>();
1✔
85
                        
86
                        for (String modulePath : moduleArray) {
1✔
87
                                if (modulePath != null && modulePath.length() > 0) {
1✔
88
                                        File file = new File(modulePath);
1✔
89
                                        if (file.exists()) {
1✔
90
                                                modulesToLoad.add(file);
×
91
                                        } else {
92
                                                // try to load the file from the classpath
93
                                                InputStream stream = ModuleUtil.class.getClassLoader().getResourceAsStream(modulePath);
1✔
94
                                                
95
                                                // expand the classpath-found file to a temporary location
96
                                                if (stream != null) {
1✔
97
                                                        try {
98
                                                                // get and make a temp directory if necessary
99
                                                                String tmpDir = System.getProperty("java.io.tmpdir");
1✔
100
                                                                File expandedFile = File.createTempFile(file.getName() + "-", ".omod", new File(tmpDir));
1✔
101
                                                                
102
                                                                // pull the name from the absolute path load attempt
103
                                                                FileOutputStream outStream = new FileOutputStream(expandedFile, false);
1✔
104
                                                                
105
                                                                // do the actual file copying
106
                                                                OpenmrsUtil.copyFile(stream, outStream);
1✔
107
                                                                
108
                                                                // add the freshly expanded file to the list of modules we're going to start up
109
                                                                modulesToLoad.add(expandedFile);
1✔
110
                                                                expandedFile.deleteOnExit();
1✔
111
                                                        }
112
                                                        catch (IOException io) {
×
113
                                                                log.error("Unable to expand classpath found module: " + modulePath, io);
×
114
                                                        }
1✔
115
                                                } else {
116
                                                        log
×
117
                                                                .error("Unable to load module at path: "
×
118
                                                                        + modulePath
119
                                                                        + " because no file exists there and it is not found on the classpath. (absolute path tried: "
120
                                                                        + file.getAbsolutePath() + ")");
×
121
                                                }
122
                                        }
123
                                }
124
                        }
125
                        
126
                        ModuleFactory.loadModules(modulesToLoad);
1✔
127
                }
128
                
129
                // start all of the modules we just loaded
130
                ModuleFactory.startModules();
1✔
131
                
132
                // some debugging info
133
                if (log.isDebugEnabled()) {
1✔
134
                        Collection<Module> modules = ModuleFactory.getStartedModules();
×
135
                        if (modules == null || modules.isEmpty()) {
×
136
                                log.debug("No modules loaded");
×
137
                        } else {
138
                                log.debug("Found and loaded {} module(s)", modules.size());
×
139
                        }
140
                }
141
                
142
                // make sure all openmrs required moduls are loaded and started
143
                checkOpenmrsCoreModulesStarted();
1✔
144
                
145
                // make sure all mandatory modules are loaded and started
146
                checkMandatoryModulesStarted();
1✔
147
        }
1✔
148
        
149
        /**
150
         * Stops the module system by calling stopModule for all modules that are currently started
151
         */
152
        public static void shutdown() {
153

154
                List<Module> modules = new ArrayList<>(ModuleFactory.getStartedModules());
1✔
155
                
156
                for (Module mod : modules) {
1✔
157
                        log.debug("stopping module: {}", mod.getModuleId());
1✔
158
                        
159
                        if (mod.isStarted()) {
1✔
160
                                ModuleFactory.stopModule(mod, true, true);
1✔
161
                        }
162
                }
1✔
163
                
164
                log.debug("done shutting down modules");
1✔
165
                
166
                // clean up the static variables just in case they weren't done before
167
                ModuleFactory.extensionMap.clear();
1✔
168
                ModuleFactory.loadedModules.invalidateAll();
1✔
169
                ModuleFactory.moduleClassLoaders.invalidateAll();
1✔
170
                ModuleFactory.startedModules.invalidateAll();
1✔
171
        }
1✔
172
        
173
        /**
174
         * Add the <code>inputStream</code> as a file in the modules repository
175
         *
176
         * @param inputStream <code>InputStream</code> to load
177
         * @return filename String of the file's name of the stream
178
         */
179
        public static File insertModuleFile(InputStream inputStream, String filename) {
180
                File folder = getModuleRepository();
×
181
                
182
                // check if module filename is already loaded
183
                if (OpenmrsUtil.folderContains(folder, filename)) {
×
184
                        throw new ModuleException(filename + " is already associated with a loaded module.");
×
185
                }
186
                
187
                File file = new File(folder.getAbsolutePath(), filename);
×
188
                
189
                try (FileOutputStream outputStream = new FileOutputStream(file)) {
×
190
                        OpenmrsUtil.copyFile(inputStream, outputStream);
×
191
                }
192
                catch (IOException e) {
×
193
                        throw new ModuleException("Can't create module file for " + filename, e);
×
194
                }
195
                finally {
196
                        try {
197
                                inputStream.close();
×
198
                        }
199
                        catch (Exception e) { /* pass */}
×
200
                }
201
                
202
                return file;
×
203
        }
204

205
        /**
206
         * Checks if the current OpenMRS version is in an array of versions.
207
         * <p>
208
         * This method calls {@link ModuleUtil#matchRequiredVersions(String, String)} internally.
209
         * </p>
210
         *
211
         * @param versions the openmrs versions to be checked against the current openmrs version
212
         * @return true if the current openmrs version is in versions otherwise false
213
         * <strong>Should</strong> return false when versions is null
214
         * <strong>Should</strong> return false when versions is empty
215
         * <strong>Should</strong> return true if current openmrs version matches one element in versions
216
         * <strong>Should</strong> return false if current openmrs version does not match any element in versions
217
         */
218
        public static boolean isOpenmrsVersionInVersions(String ...versions) {
219
                return isVersionInVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, versions);
1✔
220
        }
221

222
        /**
223
         * For testing of {@link #isOpenmrsVersionInVersions(String...)} only.
224
         * 
225
         * @param version the version
226
         * @param versions versions to match
227
         * @return true if version matches any value from versions
228
         */
229
        static boolean isVersionInVersions(String version, String ...versions) {
230
                if (versions == null || versions.length == 0) {
1✔
231
                        return false;
1✔
232
                }
233

234
                boolean result = false;
1✔
235
                for (String candidateVersion : versions) {
1✔
236
                        if (matchRequiredVersions(version, candidateVersion)) {
1✔
237
                                result = true;
1✔
238
                                break;
1✔
239
                        }
240
                }
241
                return result;
1✔
242
        }
243
        
244
        /**
245
         * This method is an enhancement of {@link #compareVersion(String, String)} and adds support for
246
         * wildcard characters and upperbounds. <br>
247
         * <br>
248
         * This method calls {@link ModuleUtil#checkRequiredVersion(String, String)} internally. <br>
249
         * <br>
250
         * The require version number in the config file can be in the following format:
251
         * <ul>
252
         * <li>1.2.3</li>
253
         * <li>1.2.*</li>
254
         * <li>1.2.2 - 1.2.3</li>
255
         * <li>1.2.* - 1.3.*</li>
256
         * </ul>
257
         * <p>
258
         * Again the possible require version number formats with their interpretation:
259
         * <ul>
260
         * <li>1.2.3 means 1.2.3 and above</li>
261
         * <li>1.2.* means any version of the 1.2.x branch. That is 1.2.0, 1.2.1, 1.2.2,... but not 1.3.0, 1.4.0</li>
262
         * <li>1.2.2 - 1.2.3 means 1.2.2 and 1.2.3 (inclusive)</li>
263
         * <li>1.2.* - 1.3.* means any version of the 1.2.x and 1.3.x branch</li>
264
         * </ul>
265
         * </p>
266
         *
267
         * @param version openmrs version number to be compared
268
         * @param versionRange value in the config file for required openmrs version
269
         * @return true if the <code>version</code> is within the <code>value</code>
270
         * <strong>Should</strong> allow ranged required version
271
         * <strong>Should</strong> allow ranged required version with wild card
272
         * <strong>Should</strong> allow ranged required version with wild card on one end
273
         * <strong>Should</strong> allow single entry for required version
274
         * <strong>Should</strong> allow required version with wild card
275
         * <strong>Should</strong> allow non numeric character required version
276
         * <strong>Should</strong> allow ranged non numeric character required version
277
         * <strong>Should</strong> allow ranged non numeric character with wild card
278
         * <strong>Should</strong> allow ranged non numeric character with wild card on one end
279
         * <strong>Should</strong> return false when openmrs version beyond wild card range
280
         * <strong>Should</strong> return false when required version beyond openmrs version
281
         * <strong>Should</strong> return false when required version with wild card beyond openmrs version
282
         * <strong>Should</strong> return false when required version with wild card on one end beyond openmrs version
283
         * <strong>Should</strong> return false when single entry required version beyond openmrs version
284
         * <strong>Should</strong> allow release type in the version
285
         * <strong>Should</strong> match when revision number is below maximum revision number
286
         * <strong>Should</strong> not match when revision number is above maximum revision number
287
         * <strong>Should</strong> correctly set upper and lower limit for versionRange with qualifiers and wild card
288
         * <strong>Should</strong> match when version has wild card plus qualifier and is within boundary
289
         * <strong>Should</strong> not match when version has wild card plus qualifier and is outside boundary
290
         * <strong>Should</strong> match when version has wild card and is within boundary
291
         * <strong>Should</strong> not match when version has wild card and is outside boundary
292
         * <strong>Should</strong> return true when required version is empty
293
         */
294
        public static boolean matchRequiredVersions(String version, String versionRange) {
295
                // There is a null check so no risk in keeping the literal on the right side
296
                if (StringUtils.isNotEmpty(versionRange)) {
1✔
297
                        String[] ranges = versionRange.split(",");
1✔
298
                        for (String range : ranges) {
1✔
299
                                // need to externalize this string
300
                                String separator = "-";
1✔
301
                                if (range.indexOf("*") > 0 || range.indexOf(separator) > 0 && (!isVersionWithQualifier(range))) {
1✔
302
                                        // if it contains "*" or "-" then we must separate those two
303
                                        // assume it's always going to be two part
304
                                        // assign the upper and lower bound
305
                                        // if there's no "-" to split lower and upper bound
306
                                        // then assign the same value for the lower and upper
307
                                        String lowerBound = range;
1✔
308
                                        String upperBound = range;
1✔
309
                                        
310
                                        int indexOfSeparator = range.indexOf(separator);
1✔
311
                                        while (indexOfSeparator > 0) {
1✔
312
                                                lowerBound = range.substring(0, indexOfSeparator);
1✔
313
                                                upperBound = range.substring(indexOfSeparator + 1);
1✔
314
                                                if (upperBound.matches("^\\s?\\d+.*")) {
1✔
315
                                                        break;
1✔
316
                                                }
317
                                                indexOfSeparator = range.indexOf(separator, indexOfSeparator + 1);
1✔
318
                                        }
319
                                        
320
                                        // only preserve part of the string that match the following format:
321
                                        // - xx.yy.*
322
                                        // - xx.yy.zz*
323
                                        lowerBound = StringUtils.remove(lowerBound, lowerBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
1✔
324
                                        upperBound = StringUtils.remove(upperBound, upperBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
1✔
325
                                        
326
                                        // if the lower contains "*" then change it to zero
327
                                        if (lowerBound.indexOf("*") > 0) {
1✔
328
                                                lowerBound = lowerBound.replaceAll("\\*", "0");
1✔
329
                                        }
330
                                        
331
                                        // if the upper contains "*" then change it to maxRevisionNumber
332
                                        if (upperBound.indexOf("*") > 0) {
1✔
333
                                                upperBound = upperBound.replaceAll("\\*", Integer.toString(Integer.MAX_VALUE));
1✔
334
                                        }
335
                                        
336
                                        int lowerReturn = compareVersion(version, lowerBound);
1✔
337
                                        
338
                                        int upperReturn = compareVersion(version, upperBound);
1✔
339
                                        
340
                                        if (lowerReturn < 0 || upperReturn > 0) {
1✔
341
                                                log.debug("Version " + version + " is not between " + lowerBound + " and " + upperBound);
1✔
342
                                        } else {
343
                                                return true;
1✔
344
                                        }
345
                                } else {
1✔
346
                                        if (compareVersion(version, range) < 0) {
1✔
347
                                                log.debug("Version " + version + " is below " + range);
1✔
348
                                        } else {
349
                                                return true;
1✔
350
                                        }
351
                                }
352
                        }
353
                }
1✔
354
                else {
355
                        //no version checking if required version is not specified
356
                        return true;
1✔
357
                }
358
                
359
                return false;
1✔
360
        }
361
        
362
        /**
363
         * This method is an enhancement of {@link #compareVersion(String, String)} and adds support for
364
         * wildcard characters and upperbounds. <br>
365
         * <br>
366
         * <br>
367
         * The require version number in the config file can be in the following format:
368
         * <ul>
369
         * <li>1.2.3</li>
370
         * <li>1.2.*</li>
371
         * <li>1.2.2 - 1.2.3</li>
372
         * <li>1.2.* - 1.3.*</li>
373
         * </ul>
374
         * <p>
375
         * Again the possible require version number formats with their interpretation:
376
         * <ul>
377
         * <li>1.2.3 means 1.2.3 and above</li>
378
         * <li>1.2.* means any version of the 1.2.x branch. That is 1.2.0, 1.2.1, 1.2.2,... but not 1.3.0, 1.4.0</li>
379
         * <li>1.2.2 - 1.2.3 means 1.2.2 and 1.2.3 (inclusive)</li>
380
         * <li>1.2.* - 1.3.* means any version of the 1.2.x and 1.3.x branch</li>
381
         * </ul>
382
         * </p>
383
         *
384
         * @param version openmrs version number to be compared
385
         * @param versionRange value in the config file for required openmrs version
386
         * @throws ModuleException if the <code>version</code> is not within the <code>value</code>
387
         * <strong>Should</strong> throw ModuleException if openmrs version beyond wild card range
388
         * <strong>Should</strong> throw ModuleException if required version beyond openmrs version
389
         * <strong>Should</strong> throw ModuleException if required version with wild card beyond openmrs version
390
         * <strong>Should</strong> throw ModuleException if required version with wild card on one end beyond openmrs
391
         *         version
392
         * <strong>Should</strong> throw ModuleException if single entry required version beyond openmrs version
393
         * <strong>Should</strong> throw ModuleException if SNAPSHOT not handled correctly
394
         * <strong>Should</strong> handle SNAPSHOT versions
395
         * <strong>Should</strong> handle ALPHA versions
396
         */
397
        public static void checkRequiredVersion(String version, String versionRange) throws ModuleException {
398
                if (!matchRequiredVersions(version, versionRange)) {
1✔
399
                        String ms = Context.getMessageSourceService().getMessage("Module.requireVersion.outOfBounds",
1✔
400
                            new String[] { versionRange, version }, Context.getLocale());
1✔
401
                        throw new ModuleException(ms);
1✔
402
                }
403
        }
1✔
404
        
405
        /**
406
         * Compares <code>version</code> to <code>value</code> version and value are strings like
407
         * 1.9.2.0 Returns <code>0</code> if either <code>version</code> or <code>value</code> is null.
408
         *
409
         * @param version String like 1.9.2.0
410
         * @param value String like 1.9.2.0
411
         * @return the value <code>0</code> if <code>version</code> is equal to the argument
412
         *         <code>value</code>; a value less than <code>0</code> if <code>version</code> is
413
         *         numerically less than the argument <code>value</code>; and a value greater than
414
         *         <code>0</code> if <code>version</code> is numerically greater than the argument
415
         *         <code>value</code>
416
         * <strong>Should</strong> correctly comparing two version numbers
417
         * <strong>Should</strong> treat SNAPSHOT as earliest version
418
         */
419
        public static int compareVersion(String version, String value) {
420
                try {
421
                        if (version == null || value == null) {
1✔
422
                                return 0;
1✔
423
                        }
424
                        
425
                        List<String> versions = new ArrayList<>();
1✔
426
                        List<String> values = new ArrayList<>();
1✔
427
                        String separator = "-";
1✔
428
                        
429
                        // strip off any qualifier e.g. "-SNAPSHOT"
430
                        int qualifierIndex = version.indexOf(separator);
1✔
431
                        if (qualifierIndex != -1) {
1✔
432
                                version = version.substring(0, qualifierIndex);
1✔
433
                        }
434
                        
435
                        qualifierIndex = value.indexOf(separator);
1✔
436
                        if (qualifierIndex != -1) {
1✔
437
                                value = value.substring(0, qualifierIndex);
1✔
438
                        }
439
                        
440
                        Collections.addAll(versions, version.split("\\."));
1✔
441
                        Collections.addAll(values, value.split("\\."));
1✔
442
                        
443
                        // match the sizes of the lists
444
                        while (versions.size() < values.size()) {
1✔
445
                                versions.add("0");
1✔
446
                        }
447
                        while (values.size() < versions.size()) {
1✔
448
                                values.add("0");
1✔
449
                        }
450
                        
451
                        for (int x = 0; x < versions.size(); x++) {
1✔
452
                                String verNum = versions.get(x).trim();
1✔
453
                                String valNum = values.get(x).trim();
1✔
454
                                Long ver = NumberUtils.toLong(verNum, 0);
1✔
455
                                Long val = NumberUtils.toLong(valNum, 0);
1✔
456
                                
457
                                int ret = ver.compareTo(val);
1✔
458
                                if (ret != 0) {
1✔
459
                                        return ret;
1✔
460
                                }
461
                        }
462
                }
463
                catch (NumberFormatException e) {
×
464
                        log.error("Error while converting a version/value to an integer: " + version + "/" + value, e);
×
465
                }
1✔
466
                
467
                // default return value if an error occurs or elements are equal
468
                return 0;
1✔
469
        }
470
        
471
        /**
472
         * Checks for qualifier version (i.e "-SNAPSHOT", "-ALPHA" etc. after maven version conventions)
473
         *
474
         * @param version String like 1.9.2-SNAPSHOT
475
         * @return true if version contains qualifier
476
         */
477
        public static boolean isVersionWithQualifier(String version) {
478
                Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.(\\d+))?(\\-([A-Za-z]+))").matcher(version);
1✔
479
                return matcher.matches();
1✔
480
        }
481
        
482
        /**
483
         * Gets the folder where modules are stored. ModuleExceptions are thrown on errors
484
         *
485
         * @return folder containing modules
486
         * <strong>Should</strong> use the runtime property as the first choice if specified
487
         * <strong>Should</strong> return the correct file if the runtime property is an absolute path
488
         */
489
        public static File getModuleRepository() {
490
                
491
                String folderName = Context.getRuntimeProperties().getProperty(ModuleConstants.REPOSITORY_FOLDER_RUNTIME_PROPERTY);
1✔
492
                if (StringUtils.isBlank(folderName)) {
1✔
493
                        AdministrationService as = Context.getAdministrationService();
1✔
494
                        folderName = as.getGlobalProperty(ModuleConstants.REPOSITORY_FOLDER_PROPERTY,
1✔
495
                            ModuleConstants.REPOSITORY_FOLDER_PROPERTY_DEFAULT);
496
                }
497
                // try to load the repository folder straight away.
498
                File folder = new File(folderName);
1✔
499
                
500
                // if the property wasn't a full path already, assume it was intended to be a folder in the
501
                // application directory
502
                if (!folder.exists()) {
1✔
503
                        folder = new File(OpenmrsUtil.getApplicationDataDirectory(), folderName);
1✔
504
                }
505
                
506
                // now create the modules folder if it doesn't exist
507
                if (!folder.exists()) {
1✔
508
                        log.warn("Module repository " + folder.getAbsolutePath() + " doesn't exist.  Creating directories now.");
1✔
509
                        folder.mkdirs();
1✔
510
                }
511
                
512
                if (!folder.isDirectory()) {
1✔
513
                        throw new ModuleException("Module repository is not a directory at: " + folder.getAbsolutePath());
×
514
                }
515
                
516
                return folder;
1✔
517
        }
518
        
519
        /**
520
         * Utility method to convert a {@link File} object to a local URL.
521
         *
522
         * @param file a file object
523
         * @return absolute URL that points to the given file
524
         * @throws MalformedURLException if file can't be represented as URL for some reason
525
         */
526
        public static URL file2url(final File file) throws MalformedURLException {
527
                if (file == null) {
1✔
528
                        return null;
1✔
529
                }
530
                try {
531
                        return file.getCanonicalFile().toURI().toURL();
1✔
532
                }
533
                catch (IOException | NoSuchMethodError ioe) {
1✔
534
                        throw new MalformedURLException("Cannot convert: " + file.getName() + " to url");
1✔
535
                }
536
        }
537
        
538
        /**
539
         * Expand the given <code>fileToExpand</code> jar to the <code>tmpModuleFile</code> directory
540
         *
541
         * If <code>name</code> is null, the entire jar is expanded. If<code>name</code> is not null,
542
         * then only that path/file is expanded.
543
         *
544
         * @param fileToExpand file pointing at a .jar
545
         * @param tmpModuleDir directory in which to place the files
546
         * @param name filename inside of the jar to look for and expand
547
         * @param keepFullPath if true, will recreate entire directory structure in tmpModuleDir
548
         *            relating to <code>name</code>. if false will start directory structure at
549
         *            <code>name</code>
550
         * <strong>Should</strong> expand entire jar if name is null
551
         * <strong>Should</strong> expand entire jar if name is empty string
552
         * <strong>Should</strong> expand directory with parent tree if name is directory and keepFullPath is true
553
         * <strong>Should</strong> expand directory without parent tree if name is directory and keepFullPath is false
554
         * <strong>Should</strong> expand file with parent tree if name is file and keepFullPath is true
555
         */
556
        public static void expandJar(File fileToExpand, File tmpModuleDir, String name, boolean keepFullPath) throws IOException {
557
                String docBase = tmpModuleDir.getAbsolutePath();
1✔
558
                try (JarFile jarFile = new JarFile(fileToExpand)) {
1✔
559
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
1✔
560
                        boolean foundName = (name == null);
1✔
561
                        
562
                        // loop over all of the elements looking for the match to 'name'
563
                        while (jarEntries.hasMoreElements()) {
1✔
564
                                JarEntry jarEntry = jarEntries.nextElement();
1✔
565
                                if (name == null || jarEntry.getName().startsWith(name)) {
1✔
566
                                        String entryName = jarEntry.getName();
1✔
567
                                        // trim out the name path from the name of the new file
568
                                        if (!keepFullPath && name != null) {
1✔
569
                                                entryName = entryName.replaceFirst(name, "");
1✔
570
                                        }
571
                                        
572
                                        // if it has a slash, it's in a directory
573
                                        int last = entryName.lastIndexOf('/');
1✔
574
                                        if (last >= 0) {
1✔
575
                                                File parent = new File(docBase, entryName.substring(0, last));
1✔
576
                                                parent.mkdirs();
1✔
577
                                                log.debug("Creating parent dirs: " + parent.getAbsolutePath());
1✔
578
                                        }
579
                                        // we don't want to "expand" directories or empty names
580
                                        if (entryName.endsWith("/") || "".equals(entryName)) {
1✔
581
                                                continue;
×
582
                                        }
583
                                        try(InputStream input = jarFile.getInputStream(jarEntry)) {
1✔
584
                                                expand(input, docBase, entryName);
1✔
585
                                        }
586
                                        foundName = true;
1✔
587
                                }
588
                        }
1✔
589
                        if (!foundName) {
1✔
590
                                log.debug("Unable to find: " + name + " in file " + fileToExpand.getAbsolutePath());
1✔
591
                        }
592
                        
593
                }
594
                catch (IOException e) {
×
595
                        log.warn("Unable to delete tmpModuleFile on error", e);
×
596
                        throw e;
×
597
                }
1✔
598
        }
1✔
599
        
600
        /**
601
         * Expand the given file in the given stream to a location (fileDir/name) The <code>input</code>
602
         * InputStream is not closed in this method
603
         *
604
         * @param input stream to read from
605
         * @param fileDir directory to copy to
606
         * @param name file/directory within the <code>fileDir</code> to which we expand
607
         *            <code>input</code>
608
         * @return File the file created by the expansion.
609
         * @throws IOException if an error occurred while copying
610
         */
611
        private static void expand(InputStream input, String fileDir, String name) throws IOException {
612
                log.debug("expanding: {}", name);
1✔
613

614
                File file = new File(fileDir, name);
1✔
615

616
                if (!file.toPath().normalize().startsWith(fileDir)) {
1✔
617
                        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.");
×
618
                }
619
                
620
                try (FileOutputStream outStream = new FileOutputStream(file)) {
1✔
621
                        OpenmrsUtil.copyFile(input, outStream);
1✔
622
                }
623
        }
1✔
624
        
625
        /**
626
         * Downloads the contents of a URL and copies them to a string (Borrowed from oreilly)
627
         *
628
         * @param url
629
         * @return InputStream of contents
630
         * <strong>Should</strong> return a valid input stream for old module urls
631
         */
632
        public static InputStream getURLStream(URL url) {
633
                InputStream in = null;
1✔
634
                try {
635
                        URLConnection uc = url.openConnection();
1✔
636
                        uc.setDefaultUseCaches(false);
1✔
637
                        uc.setUseCaches(false);
1✔
638
                        uc.setRequestProperty("Cache-Control", "max-age=0,no-cache");
1✔
639
                        uc.setRequestProperty("Pragma", "no-cache");
1✔
640
                        
641
                        log.debug("Logging an attempt to connect to: " + url);
1✔
642
                        
643
                        in = openConnectionCheckRedirects(uc);
×
644
                }
645
                catch (IOException io) {
1✔
646
                        log.warn("io while reading: " + url, io);
1✔
647
                }
×
648
                
649
                return in;
1✔
650
        }
651
        
652
        /**
653
         * Convenience method to follow http to https redirects. Will follow a total of 5 redirects,
654
         * then fail out due to foolishness on the url's part.
655
         *
656
         * @param c the {@link URLConnection} to open
657
         * @return an {@link InputStream} that is not necessarily at the same url, possibly at a 403
658
         *         redirect.
659
         * @throws IOException
660
         * @see #getURLStream(URL)
661
         */
662
        protected static InputStream openConnectionCheckRedirects(URLConnection c) throws IOException {
663
                boolean redir;
664
                int redirects = 0;
1✔
665
                InputStream in;
666
                do {
667
                        if (c instanceof HttpURLConnection) {
1✔
668
                                ((HttpURLConnection) c).setInstanceFollowRedirects(false);
1✔
669
                        }
670
                        // We want to open the input stream before getting headers
671
                        // because getHeaderField() et al swallow IOExceptions.
672
                        in = c.getInputStream();
×
673
                        redir = false;
×
674
                        if (c instanceof HttpURLConnection) {
×
675
                                HttpURLConnection http = (HttpURLConnection) c;
×
676
                                int stat = http.getResponseCode();
×
677
                                if (stat == 300 || stat == 301 || stat == 302 || stat == 303 || stat == 305 || stat == 307) {
×
678
                                        URL base = http.getURL();
×
679
                                        String loc = http.getHeaderField("Location");
×
680
                                        URL target = null;
×
681
                                        if (loc != null) {
×
682
                                                target = new URL(base, loc);
×
683
                                        }
684
                                        http.disconnect();
×
685
                                        // Redirection should be allowed only for HTTP and HTTPS
686
                                        // and should be limited to 5 redirects at most.
687
                                        if (target == null || !("http".equals(target.getProtocol()) || "https".equals(target.getProtocol()))
×
688
                                                || redirects >= 5) {
689
                                                throw new SecurityException("illegal URL redirect");
×
690
                                        }
691
                                        redir = true;
×
692
                                        c = target.openConnection();
×
693
                                        redirects++;
×
694
                                }
695
                        }
696
                } while (redir);
×
697
                return in;
×
698
        }
699
        
700
        /**
701
         * Downloads the contents of a URL and copies them to a string (Borrowed from oreilly)
702
         *
703
         * @param url
704
         * @return String contents of the URL
705
         * <strong>Should</strong> return an update rdf page for old https dev urls
706
         * <strong>Should</strong> return an update rdf page for old https module urls
707
         * <strong>Should</strong> return an update rdf page for module urls
708
         */
709
        public static String getURL(URL url) {
710
                InputStream in = null;
×
711
                ByteArrayOutputStream out = null;
×
712
                String output = "";
×
713
                try {
714
                        in = getURLStream(url);
×
715
                        if (in == null) {
×
716
                                // skip this module if updateURL is not defined
717
                                return "";
×
718
                        }
719
                        
720
                        out = new ByteArrayOutputStream();
×
721
                        OpenmrsUtil.copyFile(in, out);
×
722
                        output = out.toString(StandardCharsets.UTF_8.name());
×
723
                }
724
                catch (IOException io) {
×
725
                        log.warn("io while reading: " + url, io);
×
726
                }
727
                finally {
728
                        try {
729
                                in.close();
×
730
                        }
731
                        catch (Exception e) { /* pass */}
×
732
                        try {
733
                                out.close();
×
734
                        }
735
                        catch (Exception e) { /* pass */}
×
736
                }
737
                
738
                return output;
×
739
        }
740
        
741
        /**
742
         * Iterates over the modules and checks each update.rdf file for an update
743
         *
744
         * @return True if an update was found for one of the modules, false if none were found
745
         * @throws ModuleException
746
         */
747
        public static Boolean checkForModuleUpdates() throws ModuleException {
748
                
749
                Boolean updateFound = false;
×
750
                
751
                for (Module mod : ModuleFactory.getLoadedModules()) {
×
752
                        String updateURL = mod.getUpdateURL();
×
753
                        if (StringUtils.isNotEmpty(updateURL)) {
×
754
                                try {
755
                                        // get the contents pointed to by the url
756
                                        URL url = new URL(updateURL);
×
757
                                        if (!url.toString().endsWith(ModuleConstants.UPDATE_FILE_NAME)) {
×
758
                                                log.warn("Illegal url: " + url);
×
759
                                                continue;
×
760
                                        }
761
                                        String content = getURL(url);
×
762
                                        
763
                                        // skip empty or invalid updates
764
                                        if ("".equals(content)) {
×
765
                                                continue;
×
766
                                        }
767
                                        
768
                                        // process and parse the contents
769
                                        UpdateFileParser parser = new UpdateFileParser(content);
×
770
                                        parser.parse();
×
771
                                        
772
                                        log.debug("Update for mod: " + mod.getModuleId() + " compareVersion result: "
×
773
                                                + compareVersion(mod.getVersion(), parser.getCurrentVersion()));
×
774
                                        
775
                                        // check the update.rdf version against the installed version
776
                                        if (compareVersion(mod.getVersion(), parser.getCurrentVersion()) < 0) {
×
777
                                                if (mod.getModuleId().equals(parser.getModuleId())) {
×
778
                                                        mod.setDownloadURL(parser.getDownloadURL());
×
779
                                                        mod.setUpdateVersion(parser.getCurrentVersion());
×
780
                                                        updateFound = true;
×
781
                                                } else {
782
                                                        log.warn("Module id does not match in update.rdf:" + parser.getModuleId());
×
783
                                                }
784
                                        } else {
785
                                                mod.setDownloadURL(null);
×
786
                                                mod.setUpdateVersion(null);
×
787
                                        }
788
                                }
789
                                catch (ModuleException e) {
×
790
                                        log.warn("Unable to get updates from update.xml", e);
×
791
                                }
792
                                catch (MalformedURLException e) {
×
793
                                        log.warn("Unable to form a URL object out of: " + updateURL, e);
×
794
                                }
×
795
                        }
796
                }
×
797
                
798
                return updateFound;
×
799
        }
800
        
801
        /**
802
         * @return true/false whether the 'allow upload' or 'allow web admin' property has been turned
803
         *         on
804
         */
805
        public static Boolean allowAdmin() {
806
                
807
                Properties properties = Context.getRuntimeProperties();
×
808
                String prop = properties.getProperty(ModuleConstants.RUNTIMEPROPERTY_ALLOW_UPLOAD, null);
×
809
                if (prop == null) {
×
810
                        prop = properties.getProperty(ModuleConstants.RUNTIMEPROPERTY_ALLOW_ADMIN, "false");
×
811
                }
812
                
813
                return "true".equals(prop);
×
814
        }
815
        
816
        /**
817
         * @see ModuleUtil#refreshApplicationContext(AbstractRefreshableApplicationContext, boolean, Module)
818
         */
819
        public static AbstractRefreshableApplicationContext refreshApplicationContext(AbstractRefreshableApplicationContext ctx) {
820
                return refreshApplicationContext(ctx, false, null);
×
821
        }
822
        
823
        /**
824
         * Refreshes the given application context "properly" in OpenMRS. Will first shut down the
825
         * Context and destroy the classloader, then will refresh and set everything back up again.
826
         *
827
         * @param ctx Spring application context that needs refreshing.
828
         * @param isOpenmrsStartup if this refresh is being done at application startup.
829
         * @param startedModule the module that was just started and waiting on the context refresh.
830
         * @return AbstractRefreshableApplicationContext The newly refreshed application context.
831
         */
832
        public static AbstractRefreshableApplicationContext refreshApplicationContext(AbstractRefreshableApplicationContext ctx,
833
                boolean isOpenmrsStartup, Module startedModule) {
834
                //notify all started modules that we are about to refresh the context
835
                Set<Module> startedModules = new LinkedHashSet<>(ModuleFactory.getStartedModulesInOrder());
×
836
                for (Module module : startedModules) {
×
837
                        try {
838
                                if (module.getModuleActivator() != null) {
×
NEW
839
                                        log.debug("Run module willRefreshContext: {}", module.getModuleId());
×
840
                                        Thread.currentThread().setContextClassLoader(ModuleFactory.getModuleClassLoader(module));
×
841
                                        module.getModuleActivator().willRefreshContext();
×
842
                                }
843
                        }
844
                        catch (Exception e) {
×
845
                                log.warn("Unable to call willRefreshContext() method in the module's activator", e);
×
846
                        }
×
847
                }
×
848
                
849
                OpenmrsClassLoader.saveState();
×
850
                SchedulerUtil.shutdown();
×
851
                ServiceContext.destroyInstance();
×
852
                
853
                try {
854
                        ctx.stop();
×
855
                        ctx.close();
×
856
                }
857
                catch (Exception e) {
×
858
                        log.warn("Exception while stopping and closing context: ", e);
×
859
                        // Spring seems to be trying to refresh the context instead of /just/ stopping
860
                        // pass
861
                }
×
862
                OpenmrsClassLoader.destroyInstance();
×
863
                ctx.setClassLoader(OpenmrsClassLoader.getInstance());
×
864
                Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
×
865
                
NEW
866
                log.debug("Refreshing context");
×
UNCOV
867
                ServiceContext.getInstance().startRefreshingContext();
×
868
                try {
869
                        ctx.refresh();
×
870
                }
871
                finally {
872
                        ServiceContext.getInstance().doneRefreshingContext();
×
873
                }
NEW
874
                log.debug("Done refreshing context");
×
875
                
876
                ctx.setClassLoader(OpenmrsClassLoader.getInstance());
×
877
                Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance());
×
878
                
879
                OpenmrsClassLoader.restoreState();
×
NEW
880
                log.debug("Startup scheduler");
×
UNCOV
881
                SchedulerUtil.startup(Context.getRuntimeProperties());
×
882
                
883
                OpenmrsClassLoader.setThreadsToNewClassLoader();
×
884
                
885
                // reload the advice points that were lost when refreshing Spring
886
                log.debug("Reloading advice for all started modules: {}", startedModules.size());
×
887
                
888
                try {
889
                        //The call backs in this block may need lazy loading of objects
890
                        //which will fail because we use an OpenSessionInViewFilter whose opened session
891
                        //was closed when the application context was refreshed as above.
892
                        //So we need to open another session now. TRUNK-3739
893
                        Context.openSessionWithCurrentUser();
×
894
                        for (Module module : startedModules) {
×
895
                                if (!module.isStarted()) {
×
896
                                        continue;
×
897
                                }
898
                                
899
                                ModuleFactory.loadAdvice(module);
×
900
                                try {
901
                                        ModuleFactory.passDaemonToken(module);
×
902
                                        
903
                                        if (module.getModuleActivator() != null) {
×
NEW
904
                                                log.debug("Run module contextRefreshed: {}", module.getModuleId());
×
UNCOV
905
                                                module.getModuleActivator().contextRefreshed();
×
906
                                                try {
907
                                                        //if it is system start up, call the started method for all started modules
908
                                                        if (isOpenmrsStartup) {
×
NEW
909
                                                                log.debug("Run module started: {}", module.getModuleId());
×
UNCOV
910
                                                                module.getModuleActivator().started();
×
911
                                                        }
912
                                                        //if refreshing the context after a user started or uploaded a new module
913
                                                        else if (!isOpenmrsStartup && module.equals(startedModule)) {
×
NEW
914
                                                                log.debug("Run module started: {}", module.getModuleId());
×
UNCOV
915
                                                                module.getModuleActivator().started();
×
916
                                                        }
NEW
917
                                                        log.debug("Done running module started: {}", module.getModuleId());
×
918
                                                }
919
                                                catch (Exception e) {
×
920
                                                        log.warn("Unable to invoke started() method on the module's activator", e);
×
921
                                                        ModuleFactory.stopModule(module, true, true);
×
922
                                                }
×
923
                                        }
924
                                        
925
                                }
926
                                catch (Exception e) {
×
927
                                        log.warn("Unable to invoke method on the module's activator ", e);
×
928
                                }
×
929
                        }
×
930
                }
931
                finally {
932
                        Context.closeSessionWithCurrentUser();
×
933
                }
934
                
935
                return ctx;
×
936
        }
937
        
938
        /**
939
         * Looks at the &lt;moduleid&gt;.mandatory properties and at the currently started modules to make
940
         * sure that all mandatory modules have been started successfully.
941
         *
942
         * @throws ModuleException if a mandatory module isn't started
943
         * <strong>Should</strong> throw ModuleException if a mandatory module is not started
944
         */
945
        protected static void checkMandatoryModulesStarted() throws ModuleException {
946
                
947
                List<String> mandatoryModuleIds = getMandatoryModules();
1✔
948
                Set<String> startedModuleIds = ModuleFactory.getStartedModulesMap().keySet();
1✔
949
                
950
                mandatoryModuleIds.removeAll(startedModuleIds);
1✔
951
                
952
                // any module ids left in the list are not started
953
                if (!mandatoryModuleIds.isEmpty()) {
1✔
954
                        throw new MandatoryModuleException(mandatoryModuleIds);
1✔
955
                }
956
        }
1✔
957
        
958
        /**
959
         * Looks at the list of modules in {@link ModuleConstants#CORE_MODULES} to make sure that all
960
         * modules that are core to OpenMRS are started and have at least a minimum version that OpenMRS
961
         * needs.
962
         *
963
         * @throws ModuleException if a module that is core to OpenMRS is not started
964
         * <strong>Should</strong> throw ModuleException if a core module is not started
965
         */
966
        protected static void checkOpenmrsCoreModulesStarted() throws OpenmrsCoreModuleException {
967
                
968
                // if there is a property telling us to ignore required modules, drop out early
969
                if (ignoreCoreModules()) {
1✔
970
                        return;
1✔
971
                }
972
                
973
                // make a copy of the constant so we can modify the list
974
                Map<String, String> coreModules = new HashMap<>(ModuleConstants.CORE_MODULES);
×
975
                
976
                Collection<Module> startedModules = ModuleFactory.getStartedModulesMap().values();
×
977
                
978
                // loop through the current modules and test them
979
                for (Module mod : startedModules) {
×
980
                        String moduleId = mod.getModuleId();
×
981
                        if (coreModules.containsKey(moduleId)) {
×
982
                                String coreReqVersion = coreModules.get(moduleId);
×
983
                                if (compareVersion(mod.getVersion(), coreReqVersion) >= 0) {
×
984
                                        coreModules.remove(moduleId);
×
985
                                } else {
986
                                        log.debug("Module: " + moduleId + " is a core module and is started, but its version: "
×
987
                                                + mod.getVersion() + " is not within the required version: " + coreReqVersion);
×
988
                                }
989
                        }
990
                }
×
991
                
992
                // any module ids left in the list are not started
993
                if (coreModules.size() > 0) {
×
994
                        throw new OpenmrsCoreModuleException(coreModules);
×
995
                }
996
        }
×
997
        
998
        /**
999
         * Uses the runtime properties to determine if the core modules should be enforced or not.
1000
         *
1001
         * @return true if the core modules list can be ignored.
1002
         */
1003
        public static boolean ignoreCoreModules() {
1004
                String ignoreCoreModules = Context.getRuntimeProperties().getProperty(ModuleConstants.IGNORE_CORE_MODULES_PROPERTY,
1✔
1005
                    "false");
1006
                return Boolean.parseBoolean(ignoreCoreModules);
1✔
1007
        }
1008
        
1009
        /**
1010
         * Returns all modules that are marked as mandatory. Currently this means there is a
1011
         * &lt;moduleid&gt;.mandatory=true global property.
1012
         *
1013
         * @return list of modules ids for mandatory modules
1014
         * <strong>Should</strong> return mandatory module ids
1015
         */
1016
        public static List<String> getMandatoryModules() {
1017
                
1018
                List<String> mandatoryModuleIds = new ArrayList<>();
1✔
1019
                
1020
                try {
1021
                        List<GlobalProperty> props = Context.getAdministrationService().getGlobalPropertiesBySuffix(".mandatory");
1✔
1022
                        
1023
                        for (GlobalProperty prop : props) {
1✔
1024
                                if ("true".equalsIgnoreCase(prop.getPropertyValue())) {
1✔
1025
                                        mandatoryModuleIds.add(prop.getProperty().replace(".mandatory", ""));
1✔
1026
                                }
1027
                        }
1✔
1028
                }
1029
                catch (Exception e) {
×
1030
                        log.warn("Unable to get the mandatory module list", e);
×
1031
                }
1✔
1032
                
1033
                return mandatoryModuleIds;
1✔
1034
        }
1035
        
1036
        /**
1037
         * <pre>
1038
         * Gets the module that should handle a path. The path you pass in should be a module id (in
1039
         * path format, i.e. /ui/springmvc, not ui.springmvc) followed by a resource. Something like
1040
         * the following:
1041
         *   /ui/springmvc/css/ui.css
1042
         *
1043
         * The first running module out of the following would be returned:
1044
         *   ui.springmvc.css
1045
         *   ui.springmvc
1046
         *   ui
1047
         * </pre>
1048
         *
1049
         * @param path
1050
         * @return the running module that matches the most of the given path
1051
         * <strong>Should</strong> handle ui springmvc css ui dot css when ui dot springmvc module is running
1052
         * <strong>Should</strong> handle ui springmvc css ui dot css when ui module is running
1053
         * <strong>Should</strong> return null for ui springmvc css ui dot css when no relevant module is running
1054
         */
1055
        public static Module getModuleForPath(String path) {
1056
                int ind = path.lastIndexOf('/');
1✔
1057
                if (ind <= 0) {
1✔
1058
                        throw new IllegalArgumentException(
×
1059
                                "Input must be /moduleId/resource. Input needs a / after the first character: " + path);
1060
                }
1061
                String moduleId = path.startsWith("/") ? path.substring(1, ind) : path.substring(0, ind);
1✔
1062
                moduleId = moduleId.replace('/', '.');
1✔
1063
                // iterate over progressively shorter module ids
1064
                while (true) {
1065
                        Module mod = ModuleFactory.getStartedModuleById(moduleId);
1✔
1066
                        if (mod != null) {
1✔
1067
                                return mod;
1✔
1068
                        }
1069
                        // try the next shorter module id
1070
                        ind = moduleId.lastIndexOf('.');
1✔
1071
                        if (ind < 0) {
1✔
1072
                                break;
1✔
1073
                        }
1074
                        moduleId = moduleId.substring(0, ind);
1✔
1075
                }
1✔
1076
                return null;
1✔
1077
        }
1078
        
1079
        /**
1080
         * Takes a global path and returns the local path within the specified module. For example
1081
         * calling this method with the path "/ui/springmvc/css/ui.css" and the ui.springmvc module, you
1082
         * would get "/css/ui.css".
1083
         *
1084
         * @param module
1085
         * @param path
1086
         * @return local path
1087
         * <strong>Should</strong> handle ui springmvc css ui dot css example
1088
         */
1089
        public static String getPathForResource(Module module, String path) {
1090
                if (path.startsWith("/")) {
1✔
1091
                        path = path.substring(1);
1✔
1092
                }
1093
                return path.substring(module.getModuleIdAsPath().length());
1✔
1094
        }
1095
        
1096
        /**
1097
         * This loops over all FILES in this jar to get the package names. If there is an empty
1098
         * directory in this jar it is not returned as a providedPackage.
1099
         *
1100
         * @param file jar file to look into
1101
         * @return list of strings of package names in this jar
1102
         */
1103
        public static Collection<String> getPackagesFromFile(File file) {
1104
                
1105
                // End early if we're given a non jar file
1106
                if (!file.getName().endsWith(".jar")) {
1✔
1107
                        return Collections.emptySet();
1✔
1108
                }
1109
                
1110
                Set<String> packagesProvided = new HashSet<>();
1✔
1111
                
1112
                JarFile jar = null;
1✔
1113
                try {
1114
                        jar = new JarFile(file);
1✔
1115
                        
1116
                        Enumeration<JarEntry> jarEntries = jar.entries();
1✔
1117
                        while (jarEntries.hasMoreElements()) {
1✔
1118
                                JarEntry jarEntry = jarEntries.nextElement();
1✔
1119
                                if (jarEntry.isDirectory()) {
1✔
1120
                                        // skip over directory entries, we only care about files
1121
                                        continue;
1✔
1122
                                }
1123
                                String name = jarEntry.getName();
1✔
1124
                                
1125
                                // Skip over some folders in the jar/omod
1126
                                if (name.startsWith("lib") || name.startsWith("META-INF") || name.startsWith("web/module")) {
1✔
1127
                                        continue;
1✔
1128
                                }
1129
                                
1130
                                Integer indexOfLastSlash = name.lastIndexOf("/");
1✔
1131
                                if (indexOfLastSlash <= 0) {
1✔
1132
                                        continue;
1✔
1133
                                }
1134
                                String packageName = name.substring(0, indexOfLastSlash);
1✔
1135
                                
1136
                                packageName = packageName.replaceAll("/", ".");
1✔
1137
                                
1138
                                if (packagesProvided.add(packageName) && log.isTraceEnabled()) {
1✔
1139
                                        log.trace("Adding module's jarentry with package: " + packageName);
×
1140
                                }
1141
                        }
1✔
1142
                        
1143
                        jar.close();
1✔
1144
                }
1145
                catch (IOException e) {
×
1146
                        log.error("Error while reading file: " + file.getAbsolutePath(), e);
×
1147
                }
1148
                finally {
1149
                        if (jar != null) {
1✔
1150
                                try {
1151
                                        jar.close();
1✔
1152
                                }
1153
                                catch (IOException e) {
×
1154
                                        // Ignore quietly
1155
                                }
1✔
1156
                        }
1157
                }
1158
                
1159
                return packagesProvided;
1✔
1160
        }
1161
        
1162
        /**
1163
         * Get a resource as from the module's api jar. Api jar should be in the omod's lib folder.
1164
         * 
1165
         * @param jarFile omod file loaded as jar
1166
         * @param moduleId id of the module
1167
         * @param version version of the module
1168
         * @param resource name of a resource from the api jar
1169
         * @return resource as an input stream or <code>null</code> if resource cannot be loaded
1170
         * <strong>Should</strong> load file from api as input stream
1171
         * <strong>Should</strong> return null if api is not found
1172
         * <strong>Should</strong> return null if file is not found in api
1173
         */
1174
        public static InputStream getResourceFromApi(JarFile jarFile, String moduleId, String version, String resource) {
1175
                String apiLocation = "lib/" + moduleId + "-api-" + version + ".jar";
1✔
1176
                return getResourceFromInnerJar(jarFile, apiLocation, resource);
1✔
1177
        }
1178
        
1179
        /**
1180
         * Load resource from a jar inside a jar.
1181
         * 
1182
         * @param outerJarFile jar file that contains a jar file
1183
         * @param innerJarFileLocation inner jar file location relative to the outer jar
1184
         * @param resource path to a resource relative to the inner jar
1185
         * @return resource from the inner jar as an input stream or <code>null</code> if resource cannot be loaded
1186
         */
1187
        private static InputStream getResourceFromInnerJar(JarFile outerJarFile, String innerJarFileLocation, String resource) {
1188
                File tempFile = null;
1✔
1189
                FileOutputStream tempOut = null;
1✔
1190
                JarFile innerJarFile = null;
1✔
1191
                InputStream innerInputStream = null;
1✔
1192
                try {
1193
                        tempFile = File.createTempFile("tempFile", "jar");
1✔
1194
                        tempOut = new FileOutputStream(tempFile);
1✔
1195
                        ZipEntry innerJarFileEntry = outerJarFile.getEntry(innerJarFileLocation);
1✔
1196
                        if (innerJarFileEntry != null) {
1✔
1197
                                IOUtils.copy(outerJarFile.getInputStream(innerJarFileEntry), tempOut);
1✔
1198
                                innerJarFile = new JarFile(tempFile);
1✔
1199
                                ZipEntry targetEntry = innerJarFile.getEntry(resource);
1✔
1200
                                if (targetEntry != null) {
1✔
1201
                                        // clone InputStream to make it work after the innerJarFile is closed
1202
                                        innerInputStream = innerJarFile.getInputStream(targetEntry);
1✔
1203
                                        byte[] byteArray = IOUtils.toByteArray(innerInputStream);
1✔
1204
                                        return new ByteArrayInputStream(byteArray);
1✔
1205
                                }
1206
                        }
1207
                }
1208
                catch (IOException e) {
×
1209
                        log.error("Unable to get '" + resource + "' from '" + innerJarFileLocation + "' of '" + outerJarFile.getName()
×
1210
                                + "'", e);
1211
                }
1212
                finally {
1213
                        IOUtils.closeQuietly(tempOut);
1✔
1214
                        IOUtils.closeQuietly(innerInputStream);
1✔
1215

1216
                        // close inner jar file before attempting to delete temporary file
1217
                        try {
1218
                                if (innerJarFile != null) {
1✔
1219
                                        innerJarFile.close();
1✔
1220
                                }
1221
                        }
1222
                        catch (IOException e) {
×
1223
                                log.warn("Unable to close inner jarfile: " + innerJarFile, e);
×
1224
                        }
1✔
1225

1226
                        // delete temporary file
1227
                        if (tempFile != null && !tempFile.delete()) {
1✔
1228
                                log.warn("Could not delete temporary jarfile: " + tempFile);
×
1229
                        }
1230
                }
1231
                return null;
1✔
1232
        }
1233
        
1234
        /**
1235
         * Gets the root folder of a module's sources during development
1236
         * 
1237
         * @param moduleId the module id
1238
         * @return the module's development folder is specified, else null
1239
         */
1240
        public static File getDevelopmentDirectory(String moduleId) {
1241
                String directory = System.getProperty(moduleId + ".development.directory");
1✔
1242
                if (StringUtils.isNotBlank(directory)) {
1✔
1243
                        return new File(directory);
×
1244
                }
1245
                
1246
                return null;
1✔
1247
        }
1248
}
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