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

openmrs / openmrs-core / 20851422941

09 Jan 2026 12:07PM UTC coverage: 63.315% (+0.02%) from 63.297%
20851422941

push

github

rkorytkowski
TRUNK-6509: Do not expand jars every restart

41 of 55 new or added lines in 6 files covered. (74.55%)

19 existing lines in 3 files now uncovered.

23084 of 36459 relevant lines covered (63.31%)

0.63 hits per line

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

63.18
/api/src/main/java/org/openmrs/module/ModuleClassLoader.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.BufferedOutputStream;
13
import java.io.File;
14
import java.io.FileInputStream;
15
import java.io.FileOutputStream;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.OutputStream;
19
import java.net.MalformedURLException;
20
import java.net.URI;
21
import java.net.URISyntaxException;
22
import java.net.URL;
23
import java.net.URLClassLoader;
24
import java.net.URLStreamHandlerFactory;
25
import java.nio.charset.Charset;
26
import java.nio.file.FileSystem;
27
import java.nio.file.FileSystems;
28
import java.nio.file.Path;
29
import java.nio.file.Paths;
30
import java.security.CodeSource;
31
import java.security.ProtectionDomain;
32
import java.util.ArrayList;
33
import java.util.Arrays;
34
import java.util.Collection;
35
import java.util.Collections;
36
import java.util.Enumeration;
37
import java.util.HashMap;
38
import java.util.HashSet;
39
import java.util.LinkedHashSet;
40
import java.util.LinkedList;
41
import java.util.List;
42
import java.util.Map;
43
import java.util.Set;
44
import java.util.WeakHashMap;
45
import java.util.concurrent.ConcurrentHashMap;
46
import java.util.concurrent.atomic.AtomicBoolean;
47

48
import org.apache.commons.io.FileUtils;
49
import org.apache.commons.lang3.StringUtils;
50
import org.openmrs.api.APIException;
51
import org.openmrs.api.context.Context;
52
import org.openmrs.util.OpenmrsClassLoader;
53
import org.openmrs.util.OpenmrsConstants;
54
import org.openmrs.util.OpenmrsUtil;
55
import org.slf4j.Logger;
56
import org.slf4j.LoggerFactory;
57

58
/**
59
 * Standard implementation of module class loader. <br>
60
 * Code adapted from the Java Plug-in Framework (JPF) - LGPL - Copyright (C)<br>
61
 * 2004-2006 Dmitry Olshansky
62
 */
63
public class ModuleClassLoader extends URLClassLoader {
64
        
65
        private static final Logger log = LoggerFactory.getLogger(ModuleClassLoader.class);
1✔
66
        
67
        private final Module module;
68
        
69
        private Module[] requiredModules;
70
        
71
        private Module[] awareOfModules;
72
        
73
        private boolean probeParentLoaderLast = true;
1✔
74
        
75
        private final Set<String> providedPackages = new LinkedHashSet<>();
1✔
76
        
77
        private boolean disposed = false;
1✔
78
        
79
        private static final Map<String, File> libCacheFolders = new ConcurrentHashMap<>();
1✔
80
        
81
        /**
82
         * @param module Module
83
         * @param urls resources "managed" by this class loader
84
         * @param parent parent class loader
85
         * @param factory URL stream handler factory
86
         * @see URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader,
87
         *      java.net.URLStreamHandlerFactory)
88
         */
89
        protected ModuleClassLoader(final Module module, final List<URL> urls, final ClassLoader parent,
90
            final URLStreamHandlerFactory factory) {
91
                super(urls.toArray(new URL[urls.size()]), parent, factory);
1✔
92
                
93
                if (parent instanceof OpenmrsClassLoader) {
1✔
94
                        throw new IllegalArgumentException("Parent must not be OpenmrsClassLoader nor null");
×
95
                } else if (parent instanceof ModuleClassLoader) {
1✔
96
                        throw new IllegalArgumentException("Parent must not be ModuleClassLoader");
×
97
                }
98
                
99
                log.debug("URLs length: {}", urls.size());
1✔
100
                this.module = module;
1✔
101
                requiredModules = collectRequiredModuleImports(module);
1✔
102
                awareOfModules = collectAwareOfModuleImports(module);
1✔
103
        }
1✔
104
        
105
        /**
106
         * @param module the <code>Module</code> to load
107
         * @param urls <code>List&lt;URL&gt;</code> of the resources "managed" by this class loader
108
         * @param parent parent <code>ClassLoader</code>
109
         * @see URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader)
110
         */
111
        protected ModuleClassLoader(final Module module, final List<URL> urls, final ClassLoader parent) {
112
                this(module, urls, parent, null);
1✔
113
                
114
                File devDir = ModuleUtil.getDevelopmentDirectory(module.getModuleId());
1✔
115
                if (devDir != null) {
1✔
116
                        File[] fileList = devDir.listFiles();
×
117
                        if (fileList == null) {
×
118
                                return;
×
119
                        }
120
                        for (File file : fileList) {
×
121
                                if (!file.isDirectory()) {
×
122
                                        continue;
×
123
                                }
124
                                File dir = Paths.get(devDir.getAbsolutePath(), file.getName(), "target", "classes").toFile();
×
125
                                if (dir.exists()) {
×
126
                                        Collection<File> files = FileUtils.listFiles(dir, new String[] { "class" }, true);
×
127
                                        addClassFilePackages(files, dir.getAbsolutePath().length() + 1);
×
128
                                }
129
                        }
130
                } else {
×
131
                        for (URL url : urls) {
1✔
132
                                providedPackages.addAll(ModuleUtil.getPackagesFromFile(OpenmrsUtil.url2file(url)));
1✔
133
                        }
1✔
134
                }
135
        }
1✔
136
        
137
        private void addClassFilePackages(Collection<File> files, int dirLength) {
138
                for (File file : files) {
×
139
                        String name = file.getAbsolutePath().substring(dirLength);
×
140
                        Integer indexOfLastSlash = name.lastIndexOf(File.separator);
×
141
                        if (indexOfLastSlash > 0) {
×
142
                                String packageName = name.substring(0, indexOfLastSlash);
×
143
                                packageName = packageName.replace(File.separator, ".");
×
144
                                providedPackages.add(packageName);
×
145
                                
146
                        }
147
                }
×
148
        }
×
149
        
150
        /**
151
         * @param module the <code>Module</code> to load
152
         * @param urls <code>List&lt;URL&gt;</code> of thee resources "managed" by this class loader
153
         * @see URLClassLoader#URLClassLoader(java.net.URL[])
154
         */
155
        protected ModuleClassLoader(final Module module, final List<URL> urls) {
156
                this(module, urls, null);
×
157
        }
×
158
        
159
        /**
160
         * Creates class instance configured to load classes and resources for given module.
161
         *
162
         * @param module the <code>Module</code> to load
163
         * @param parent parent <code>ClassLoader</code>
164
         */
165
        public ModuleClassLoader(final Module module, final ClassLoader parent) {
166
                this(module, getUrls(module), parent);
1✔
167
        }
1✔
168
        
169
        /**
170
         * @return returns this classloader's module
171
         */
172
        public Module getModule() {
173
                return module;
1✔
174
        }
175
        
176
        public boolean isDisposed() {
177
                return disposed;
1✔
178
        }
179
        
180
        /**
181
         * Get the base class url of the given <code>cls</code>. Used for checking against system class
182
         * loads vs classloader loads
183
         *
184
         * @param cls Class name
185
         * @return URL to the class
186
         */
187
        private static URL getClassBaseUrl(final Class<?> cls) {
188
                ProtectionDomain pd = cls.getProtectionDomain();
1✔
189
                
190
                if (pd != null) {
1✔
191
                        CodeSource cs = pd.getCodeSource();
1✔
192
                        if (cs != null) {
1✔
193
                                return cs.getLocation();
1✔
194
                        }
195
                        
196
                }
197
                
198
                return null;
1✔
199
        }
200
        
201
        /**
202
         * Get all urls for all files in the given <code>module</code>
203
         *
204
         * @param module Module in which to look
205
         * @return List&lt;URL&gt; of all urls found (and cached) in the module
206
         */
207
        private static List<URL> getUrls(final Module module) {
208
                List<URL> result = new LinkedList<>();
1✔
209
                
210
                //if in dev mode, add development folder to the classpath
211
                List<String> devFolderNames = new ArrayList<>();
1✔
212
                File devDir = ModuleUtil.getDevelopmentDirectory(module.getModuleId());
1✔
213
                try {
214
                        if (devDir != null) {
1✔
215
                                File[] fileList = devDir.listFiles();
×
216
                                if (fileList != null) {
×
217
                                        for (File file : fileList) {
×
218
                                                if (!file.isDirectory()) {
×
219
                                                        continue;
×
220
                                                }
221
                                                File dir = Paths.get(devDir.getAbsolutePath(), file.getName(), "target", "classes").toFile();
×
222
                                                if (dir.exists()) {
×
223
                                                        result.add(dir.toURI().toURL());
×
224
                                                        devFolderNames.add(file.getName());
×
225
                                                }
226
                                        }
227
                                }
228
                        }
229
                }
230
                catch (MalformedURLException ex) {
×
231
                        log.error("Failed to add development folder to the classpath", ex);
×
232
                }
1✔
233
                
234
                File tmpModuleDir = getLibCacheFolderForModule(module);
1✔
235
                
236
                //add module jar to classpath only if we are not in dev mode
237
                if (devDir == null) {
1✔
238
                        File tmpModuleJar = new File(tmpModuleDir, module.getModuleId() + ".jar");
1✔
239
                        
240
                        if (!tmpModuleJar.exists()) {
1✔
241
                                try {
242
                                        tmpModuleJar.createNewFile();
1✔
243
                                }
244
                                catch (IOException io) {
×
245
                                        log.warn("Unable to create tmpModuleFile", io);
×
246
                                }
1✔
247
                        }
248
                        
249
                        // copy the module jar into that temporary folder
250
                        FileInputStream in = null;
1✔
251
                        FileOutputStream out = null;
1✔
252
                        try {
253
                                in = new FileInputStream(module.getFile());
1✔
254
                                out = new FileOutputStream(tmpModuleJar);
1✔
255
                                OpenmrsUtil.copyFile(in, out);
1✔
256
                        }
257
                        catch (IOException io) {
×
258
                                log.warn("Unable to copy tmpModuleFile", io);
×
259
                        }
260
                        finally {
261
                                try {
262
                                        in.close();
1✔
263
                                }
264
                                catch (Exception e) { /* pass */}
1✔
265
                                try {
266
                                        out.close();
1✔
267
                                }
268
                                catch (Exception e) { /* pass */}
1✔
269
                        }
270
                        
271
                        // add the module jar as a url in the classpath of the classloader
272
                        URL moduleFileURL;
273
                        try {
274
                                moduleFileURL = ModuleUtil.file2url(tmpModuleJar);
1✔
275
                                result.add(moduleFileURL);
1✔
276
                        }
277
                        catch (MalformedURLException e) {
×
278
                                log.warn("Unable to add files from module to URL list: " + module.getModuleId(), e);
×
279
                        }
1✔
280
                }
281
                
282
                // add each defined jar in the /lib folder, add as a url in the classpath of the classloader
283
                try {
284
                        log.debug("Expanding /lib folder in module");
1✔
285
                        
286
                        ModuleUtil.expandJar(module.getFile(), tmpModuleDir, "lib", true);
1✔
287
                        File libdir = new File(tmpModuleDir, "lib");
1✔
288
                        
289
                        if (libdir != null && libdir.exists()) {
1✔
290
                                Map<String, String> startedRelatedModules = new HashMap<>();
1✔
291
                                for (Module requiredModule : collectRequiredModuleImports(module)) {
1✔
292
                                        startedRelatedModules.put(requiredModule.getModuleId(), requiredModule.getVersion());
1✔
293
                                }
294
                                for (Module awareOfModule : collectAwareOfModuleImports(module)) {
1✔
295
                                        startedRelatedModules.put(awareOfModule.getModuleId(), awareOfModule.getVersion());
×
296
                                }
297
                                
298
                                // recursively get files
299
                                Collection<File> files = FileUtils.listFiles(libdir, new String[] { "jar" }, true);
1✔
300
                                for (File file : files) {
1✔
301
                                        
302
                                        //if in dev mode, do not put the module source jar files in the class path
303
                                        if (devDir != null) {
1✔
304
                                                boolean jarForDevFolder = false;
×
305
                                                for (String folderName : devFolderNames) {
×
306
                                                        if (file.getName().startsWith(module.getModuleId() + "-" + folderName + "-")) {
×
307
                                                                //e.g uiframework-api-3.3-SNAPSHOT.jar, webservices.rest-omod-common-2.14-SNAPSHOT.jar
308
                                                                //webservices.rest-omod-1.11-2.14-SNAPSHOT.jar, webservices.rest-omod-1.10-2.14-SNAPSHOT.jar, etc
309
                                                                jarForDevFolder = true;
×
310
                                                                break;
×
311
                                                        }
312
                                                }
×
313
                                                
314
                                                if (jarForDevFolder) {
×
315
                                                        continue;
×
316
                                                }
317
                                        }
318
                                        
319
                                        URL fileUrl = ModuleUtil.file2url(file);
1✔
320
                                        
321
                                        boolean include = shouldResourceBeIncluded(module, fileUrl, OpenmrsConstants.OPENMRS_VERSION_SHORT,
1✔
322
                                            startedRelatedModules);
323
                                        
324
                                        if (include) {
1✔
325
                                                log.debug("Including file in classpath: {}", fileUrl);
1✔
326
                                                result.add(fileUrl);
1✔
327
                                        } else {
328
                                                log.debug("Excluding file from classpath: {}", fileUrl);
×
329
                                        }
330
                                }
1✔
331
                        }
332
                }
333
                catch (MalformedURLException e) {
×
334
                        log.warn("Error while adding module 'lib' folder to URL result list");
×
335
                }
336
                catch (IOException io) {
×
337
                        log.warn("Error while expanding lib folder", io);
×
338
                }
1✔
339
                
340
                // add each xml document to the url list
341
                
342
                return result;
1✔
343
        }
344
        
345
        /**
346
         * Determines whether or not the given resource should be available on the classpath based on
347
         * OpenMRS version and/or modules' version. It uses the conditionalResources section specified
348
         * in config.xml. Resources that are not mentioned as conditional resources are included by
349
         * default. All conditions for a conditional resource to be included must match.
350
         *
351
         * @param module
352
         * @param fileUrl
353
         * @return true if it should be included <strong>Should</strong> return true if file matches and
354
         *         openmrs version matches <strong>Should</strong> return false if file matches but
355
         *         openmrs version does not <strong>Should</strong> return true if file does not match
356
         *         and openmrs version does not match <strong>Should</strong> return true if file
357
         *         matches and module version matches <strong>Should</strong> return false if file
358
         *         matches and module version does not match <strong>Should</strong> return false if
359
         *         file matches and openmrs version matches but module version does not match
360
         *         <strong>Should</strong> return false if file matches and module not found
361
         *         <strong>Should</strong> return true if file does not match and module version does
362
         *         not match
363
         */
364
        static boolean shouldResourceBeIncluded(Module module, URL fileUrl, String openmrsVersion,
365
                Map<String, String> startedRelatedModules) {
366
                //all resources are included by default
367
                boolean include = true;
1✔
368
                
369
                for (ModuleConditionalResource conditionalResource : module.getConditionalResources()) {
1✔
370
                        if (isMatchingConditionalResource(module, fileUrl, conditionalResource)) {
1✔
371
                                //if a resource matches a path of contidionalResource then it must meet all conditions
372
                                include = false;
1✔
373
                                
374
                                //openmrsPlatformVersion is optional
375
                                if (StringUtils.isNotBlank(conditionalResource.getOpenmrsPlatformVersion())) {
1✔
376
                                        include = ModuleUtil.matchRequiredVersions(openmrsVersion,
1✔
377
                                            conditionalResource.getOpenmrsPlatformVersion());
1✔
378
                                        
379
                                        if (!include) {
1✔
380
                                                return false;
1✔
381
                                        }
382
                                }
383
                                
384
                                //modules are optional
385
                                if (conditionalResource.getModules() != null) {
1✔
386
                                        for (ModuleConditionalResource.ModuleAndVersion conditionalModuleResource : conditionalResource
1✔
387
                                                .getModules()) {
1✔
388
                                                if ("!".equals(conditionalModuleResource.getVersion())) {
1✔
389
                                                        include = !ModuleFactory.isModuleStarted(conditionalModuleResource.getModuleId());
1✔
390
                                                        if (!include) {
1✔
391
                                                                return false;
1✔
392
                                                        }
393
                                                } else {
394
                                                        String moduleVersion = startedRelatedModules.get(conditionalModuleResource.getModuleId());
1✔
395
                                                        if (moduleVersion != null) {
1✔
396
                                                                include = ModuleUtil.matchRequiredVersions(moduleVersion,
1✔
397
                                                                    conditionalModuleResource.getVersion());
1✔
398
                                                                
399
                                                                if (!include) {
1✔
400
                                                                        return false;
1✔
401
                                                                }
402
                                                        }
403
                                                }
404
                                        }
1✔
405
                                        
406
                                }
407
                        }
408
                }
1✔
409
                
410
                return include;
1✔
411
        }
412
        
413
        static boolean isMatchingConditionalResource(Module module, URL fileUrl, ModuleConditionalResource conditionalResource) {
414
                FileSystem fileSystem = FileSystems.getDefault();
1✔
415
                if (ModuleUtil.matchRequiredVersions(module.getConfigVersion(), "2.0")) {
1✔
416
                        return fileSystem.getPathMatcher(String.format("glob:**/%s", preprocessGlobPattern(conditionalResource.getPath())))
1✔
417
                                .matches(Paths.get(fileUrl.getPath()));
1✔
418
                }
419
                return fileUrl.getPath().matches(".*" + conditionalResource.getPath() + "$");
1✔
420
        }
421

422
        private static String preprocessGlobPattern(String globPattern) {
423
                if (globPattern == null || globPattern.isEmpty()) {
1✔
424
                        return "";
×
425
                }
426

427
                globPattern = globPattern.replace("\\", "/");
1✔
428
                globPattern = globPattern.replaceAll("//+", "/");
1✔
429

430
                // Remove "file:" prefix if present
431
                if (globPattern.startsWith("file:/")) {
1✔
432
                        globPattern = globPattern.substring(5);
×
433
                }
434
                
435
                // Remove drive letter if present (e.g., C:/)
436
                if (globPattern.matches("^[a-zA-Z]:/.*")) {
1✔
437
                        globPattern = globPattern.substring(2);
1✔
438
                }
439
                
440
                if (globPattern.startsWith("/")) {
1✔
441
                        globPattern = globPattern.substring(1);
1✔
442
                }
443

444
                return globPattern;
1✔
445
        }
446
        
447
        /**
448
         * Get the library cache folder for the given module. Each module has a different cache folder
449
         * to ease cleanup when unloading a module while openmrs is running.
450
         * <p>
451
         * The method persists lastModified of a module in .moduleLastModified file. If the value changed, 
452
         * the dir will be deleted and re-created.
453
         *
454
         * @param module Module which the cache will be used for
455
         * @return File directory where the files will be placed
456
         */
457
        public static File getLibCacheFolderForModule(Module module) {
458
                File libCacheFolder = libCacheFolders.get(module.getModuleId());
1✔
459
                if (libCacheFolder != null) {
1✔
460
                        return libCacheFolder;
1✔
461
                }
462
                
463
                synchronized (libCacheFolders) {
1✔
464
                        libCacheFolder = libCacheFolders.get(module.getModuleId());
1✔
465
                        if (libCacheFolder != null) {
1✔
NEW
466
                                return libCacheFolder;
×
467
                        }
468

469
                        // each module gets its own folder named /moduleId/
470
                        File tmpModuleDir = new File(OpenmrsClassLoader.getLibCacheFolder(), module.getModuleId());
1✔
471

472
                        long moduleLastModified = module.getFile().lastModified();
1✔
473
                        File moduleLastModifiedFile = new File(tmpModuleDir, ".moduleLastModified");
1✔
474
                        
475
                        if (Context.isOptimizedStartup() && moduleLastModifiedFile.exists()) {
1✔
476
                                // Re-create tmpModuleDir if module changed
477
                                try {
478
                                        String savedLastModified = FileUtils.readFileToString(moduleLastModifiedFile,
1✔
479
                                                Charset.defaultCharset());
1✔
480
                                        if (!Long.valueOf(savedLastModified).equals(moduleLastModified)) {
1✔
481
                                                log.debug("Deleting {} since the module was modified", tmpModuleDir.getAbsolutePath());
1✔
482
                                                tmpModuleDir.delete();
1✔
483
                                        }
NEW
484
                                } catch (IOException | NumberFormatException e) {
×
NEW
485
                                        log.warn("Error while reading module last modified file: {}", moduleLastModifiedFile, e);
×
486
                                }
1✔
487
                        } else {
488
                                log.debug("Optimized startup disabled or {} does not exist, deleting {}", moduleLastModifiedFile, 
1✔
489
                                        tmpModuleDir);
490
                                tmpModuleDir.delete();
1✔
491
                        }
492

493
                        tmpModuleDir.mkdirs();
1✔
494
                        libCacheFolders.put(module.getModuleId(), tmpModuleDir);
1✔
495

496
                        if (moduleLastModified != 0L) {
1✔
497
                                try {
498
                                        FileUtils.writeStringToFile(moduleLastModifiedFile, moduleLastModified + "", Charset.defaultCharset());
1✔
NEW
499
                                } catch (IOException e) {
×
NEW
500
                                        log.warn("Error while writing module last modified file: {}", moduleLastModifiedFile, e);
×
501
                                }
1✔
502
                        }
503

504
                        return tmpModuleDir;
1✔
505
                }
506
        }
507
        
508
        /**
509
         * Get all urls for the given <code>module</code> that are not already in the
510
         * <code>existingUrls</code>
511
         *
512
         * @param module Module in which to get urls
513
         * @param existingUrls Array of URLs to skip
514
         * @return List&lt;URL&gt; of new unique urls
515
         * @see #getUrls(Module)
516
         */
517
        private static List<URL> getUrls(final Module module, final URL[] existingUrls) {
518
                List<URL> urls = Arrays.asList(existingUrls);
×
519
                List<URL> result = new LinkedList<>();
×
520
                for (URL url : getUrls(module)) {
×
521
                        if (!urls.contains(url)) {
×
522
                                result.add(url);
×
523
                        }
524
                }
×
525
                return result;
×
526
        }
527
        
528
        /**
529
         * Get and cache the imports for this module. The imports should just be the modules that set as
530
         * "required" by this module
531
         */
532
        protected static Module[] collectRequiredModuleImports(Module module) {
533
                // collect imported modules (exclude duplicates)
534
                //<module ID, Module>
535
                Map<String, Module> publicImportsMap = new WeakHashMap<>();
1✔
536
                
537
                for (String requiredPackage : module.getRequiredModules()) {
1✔
538
                        Module requiredModule = ModuleFactory.getModuleByPackage(requiredPackage);
1✔
539
                        if (ModuleFactory.isModuleStarted(requiredModule)) {
1✔
540
                                publicImportsMap.put(requiredModule.getModuleId(), requiredModule);
1✔
541
                        }
542
                }
1✔
543
                return publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
1✔
544
                
545
        }
546
        
547
        /**
548
         * Get and cache the imports for this module. The imports should just be the modules that set as
549
         * "aware of" by this module
550
         */
551
        protected static Module[] collectAwareOfModuleImports(Module module) {
552
                // collect imported modules (exclude duplicates)
553
                //<module ID, Module>
554
                Map<String, Module> publicImportsMap = new WeakHashMap<>();
1✔
555
                
556
                for (String awareOfPackage : module.getAwareOfModules()) {
1✔
557
                        Module awareOfModule = ModuleFactory.getModuleByPackage(awareOfPackage);
1✔
558
                        if (ModuleFactory.isModuleStarted(awareOfModule)) {
1✔
559
                                publicImportsMap.put(awareOfModule.getModuleId(), awareOfModule);
×
560
                        }
561
                }
1✔
562
                return publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
1✔
563
                
564
        }
565
        
566
        /**
567
         * @see org.openmrs.module.ModuleClassLoader#modulesSetChanged()
568
         */
569
        protected void modulesSetChanged() {
570
                List<URL> newUrls = getUrls(getModule(), getURLs());
×
571
                for (URL u : newUrls) {
×
572
                        addURL(u);
×
573
                }
×
574
                
575
                if (log.isDebugEnabled()) {
×
576
                        StringBuilder buf = new StringBuilder();
×
577
                        buf.append("New code URL's populated for module ").append(getModule()).append(":\r\n");
×
578
                        for (URL u : newUrls) {
×
579
                                buf.append("\t");
×
580
                                buf.append(u);
×
581
                                buf.append("\r\n");
×
582
                        }
×
583
                        log.debug(buf.toString());
×
584
                }
585
                requiredModules = collectRequiredModuleImports(getModule());
×
586
                awareOfModules = collectAwareOfModuleImports(getModule());
×
587
        }
×
588
        
589
        /**
590
         * @see org.openmrs.module.ModuleFactory#stopModule(Module, boolean)
591
         */
592
        public void dispose() {
593
                log.debug("Disposing of ModuleClassLoader: {}", this);
1✔
594
                libCacheFolders.remove(getModule().getModuleId());
1✔
595

596
                requiredModules = null;
1✔
597
                awareOfModules = null;
1✔
598
                disposed = true;
1✔
599
        }
1✔
600
        
601
        /**
602
         * Allow the probe parent loader last variable to be set. Usually this is set to true to allow
603
         * modules to override and create their own classes
604
         *
605
         * @param value boolean true/false whether or not to look at the parent classloader last
606
         */
607
        public void setProbeParentLoaderLast(final boolean value) {
608
                probeParentLoaderLast = value;
×
609
        }
×
610
        
611
        /**
612
         * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
613
         */
614
        @Override
615
        protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
616
                // Check if the class has already been loaded by this class loader
617
                Class<?> result = findLoadedClass(name);
1✔
618
                if (result == null) {
1✔
619
                        if (probeParentLoaderLast) {
1✔
620
                                try {
621
                                        result = loadClass(name, resolve, this, null);
1✔
622
                                }
623
                                catch (ClassNotFoundException cnfe) {
1✔
624
                                        // Continue trying...
625
                                }
1✔
626
                                
627
                                if (result == null && getParent() != null) {
1✔
628
                                        result = getParent().loadClass(name);
1✔
629
                                }
630
                        } else {
631
                                try {
632
                                        if (getParent() != null) {
×
633
                                                result = getParent().loadClass(name);
×
634
                                        }
635
                                }
636
                                catch (ClassNotFoundException cnfe) {
×
637
                                        // Continue trying...
638
                                }
×
639
                                
640
                                if (result == null) {
×
641
                                        result = loadClass(name, resolve, this, null);
×
642
                                }
643
                        }
644
                }
645
                
646
                if (resolve) {
1✔
647
                        resolveClass(result);
×
648
                }
649
                
650
                return result;
1✔
651
        }
652
        
653
        /**
654
         * Custom loadClass implementation to allow for loading from a given ModuleClassLoader and skip
655
         * the modules that have been tried already
656
         * 
657
         * @param name String path and name of the class to load
658
         * @param resolve boolean whether or not to resolve this class before returning
659
         * @param requestor ModuleClassLoader with which to try loading
660
         * @param seenModules Set&lt;String&gt; moduleIds that have been tried already
661
         * @return Class that has been loaded
662
         * @throws ClassNotFoundException if no class found
663
         */
664
        protected synchronized Class<?> loadClass(final String name, final boolean resolve, final ModuleClassLoader requestor,
665
                Set<String> seenModules) throws ClassNotFoundException {
666
                
667
                if (log.isTraceEnabled()) {
1✔
668
                        log.trace("Loading " + name + " " + getModule() + ", seenModules: " + seenModules + ", requestor: " + requestor
×
669
                                + ", resolve? " + resolve);
670
                        StringBuilder output = new StringBuilder();
×
671
                        for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
×
672
                                if (element.getClassName().contains("openmrs")) {
×
673
                                        output.append("+ ");
×
674
                                }
675
                                output.append(element);
×
676
                                output.append("\n");
×
677
                        }
678
                        log.trace("Stacktrace: " + output.toString());
×
679
                }
680
                
681
                // Check if we already tried this class loader
682
                if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) {
1✔
683
                        throw new ClassNotFoundException("Can't load class " + name + " from module " + getModule().getModuleId()
×
684
                                + ". It has been tried before.");
685
                }
686
                
687
                // Make sure the module is started
688
                if ((this != requestor) && !ModuleFactory.isModuleStarted(getModule())) {
1✔
689
                        String msg = "Can't load class " + name + ", because module " + getModule().getModuleId()
×
690
                                + " is not yet started.";
691
                        log.warn(msg);
×
692
                        
693
                        throw new ClassNotFoundException(msg);
×
694
                }
695
                
696
                // Check if the class has already been loaded by this class loader
697
                Class<?> result = findLoadedClass(name);
1✔
698
                
699
                // Try loading the class with this class loader 
700
                if (result == null) {
1✔
701
                        try {
702
                                result = findClass(name);
1✔
703
                        }
704
                        catch (ClassNotFoundException e) {
1✔
705
                                // Continue trying...
706
                        }
1✔
707
                }
708
                
709
                // We were able to "find" a class
710
                if (result != null) {
1✔
711
                        checkClassVisibility(result, requestor);
1✔
712
                        
713
                        return result;
1✔
714
                }
715
                
716
                // Look through this module's imports to see if the class
717
                // can be loaded from them.
718
                
719
                if (seenModules == null) {
1✔
720
                        seenModules = new HashSet<>();
1✔
721
                }
722
                
723
                // Add this module to the list of modules we've tried already
724
                seenModules.add(getModule().getModuleId());
1✔
725
                
726
                List<Module> importedModules = new ArrayList<>();
1✔
727
                if (requiredModules != null) {
1✔
728
                        Collections.addAll(importedModules, requiredModules);
1✔
729
                }
730
                if (awareOfModules != null) {
1✔
731
                        Collections.addAll(importedModules, awareOfModules);
1✔
732
                }
733
                
734
                for (Module importedModule : importedModules) {
1✔
735
                        if (seenModules.contains(importedModule.getModuleId())) {
1✔
736
                                continue;
×
737
                        }
738
                        
739
                        ModuleClassLoader moduleClassLoader = ModuleFactory.getModuleClassLoader(importedModule);
1✔
740
                        
741
                        // Module class loader may be null if module has not been started yet
742
                        if (moduleClassLoader != null) {
1✔
743
                                try {
744
                                        result = moduleClassLoader.loadClass(name, resolve, requestor, seenModules);
1✔
745
                                        
746
                                        return result;
1✔
747
                                }
748
                                catch (ClassNotFoundException e) {
1✔
749
                                        // Continue trying...
750
                                }
751
                        }
752
                }
1✔
753
                
754
                throw new ClassNotFoundException(name);
1✔
755
        }
756
        
757
        /**
758
         * Checking the given class's visibility in this module
759
         *
760
         * @param cls Class to check
761
         * @param requestor ModuleClassLoader to check against
762
         * @throws ClassNotFoundException
763
         */
764
        protected void checkClassVisibility(final Class<?> cls, final ModuleClassLoader requestor)
765
                throws ClassNotFoundException {
766
                
767
                if (this == requestor) {
1✔
768
                        return;
1✔
769
                }
770
                
771
                URL lib = getClassBaseUrl(cls);
1✔
772
                
773
                if (lib == null) {
1✔
774
                        // cls is a system class
775
                        return;
1✔
776
                }
777
                
778
                ClassLoader loader = cls.getClassLoader();
1✔
779
                
780
                if (!(loader instanceof ModuleClassLoader)) {
1✔
781
                        return;
1✔
782
                }
783
                
784
                if (loader != this) {
1✔
785
                        ((ModuleClassLoader) loader).checkClassVisibility(cls, requestor);
×
786
                }
787
        }
1✔
788
        
789
        /**
790
         * @see java.lang.ClassLoader#findLibrary(java.lang.String)
791
         */
792
        @Override
793
        protected String findLibrary(final String name) {
794
                if ((name == null) || "".equals(name.trim())) {
×
795
                        return null;
×
796
                }
797
                
798
                if (log.isTraceEnabled()) {
×
799
                        log.trace("findLibrary(String): name=" + name + ", this=" + this);
×
800
                }
801
                String libname = System.mapLibraryName(name);
×
802
                String result = null;
×
803
                
804
                if (log.isTraceEnabled()) {
×
805
                        log.trace(
×
806
                            "findLibrary(String): name=" + name + ", libname=" + libname + ", result=" + result + ", this=" + this);
807
                }
808
                
809
                return result;
×
810
        }
811
        
812
        /**
813
         * If a resource is found within a jar, that jar URL is converted to a temporary file and a URL
814
         * to that is returned
815
         *
816
         * @see java.lang.ClassLoader#findResource(java.lang.String)
817
         */
818
        @Override
819
        public URL findResource(final String name) {
820
                URL result = findResource(name, this, null);
1✔
821
                
822
                return expandIfNecessary(result);
1✔
823
        }
824
        
825
        /**
826
         * @see java.lang.ClassLoader#findResources(java.lang.String)
827
         */
828
        @Override
829
        public Enumeration<URL> findResources(final String name) throws IOException {
830
                List<URL> result = new LinkedList<>();
1✔
831
                findResources(result, name, this, null);
1✔
832
                
833
                // expand all of the "jar" urls
834
                for (URL url : result) {
1✔
835
                        url = expandIfNecessary(url);
1✔
836
                }
1✔
837
                
838
                return Collections.enumeration(result);
1✔
839
        }
840
        
841
        /**
842
         * Find a resource (image, file, etc) in the module structure
843
         *
844
         * @param name String path and name of the file
845
         * @param requestor ModuleClassLoader in which to look
846
         * @param seenModules Set&lt;String&gt; modules that have been checked already
847
         * @return URL to resource
848
         * @see #findResource(String)
849
         */
850
        protected URL findResource(final String name, final ModuleClassLoader requestor, Set<String> seenModules) {
851
                if (log.isTraceEnabled() && name != null && name.contains("starter")) {
1✔
852
                        if (seenModules != null) {
×
853
                                log.trace("seenModules.size: " + seenModules.size());
×
854
                        }
855
                        log.trace("name: " + name);
×
856
                        for (URL url : getURLs()) {
×
857
                                log.trace("url: " + url);
×
858
                        }
859
                }
860
                
861
                if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) {
1✔
862
                        return null;
×
863
                }
864
                
865
                URL result = super.findResource(name);
1✔
866
                // found resource in this module class path
867
                if (result != null) {
1✔
868
                        if (isResourceVisible(name, result, requestor)) {
×
869
                                return result;
×
870
                        }
871
                        log.debug("Resource is not visible");
×
872
                        return null;
×
873
                }
874
                
875
                if (seenModules == null) {
1✔
876
                        seenModules = new HashSet<>();
1✔
877
                }
878
                
879
                seenModules.add(getModule().getModuleId());
1✔
880
                
881
                if (requiredModules != null) {
1✔
882
                        for (Module publicImport : requiredModules) {
1✔
883
                                if (seenModules.contains(publicImport.getModuleId())) {
1✔
884
                                        continue;
×
885
                                }
886
                                ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport);
1✔
887
                                
888
                                if (mcl != null) {
1✔
889
                                        result = mcl.findResource(name, requestor, seenModules);
1✔
890
                                }
891
                                
892
                                if (result != null) {
1✔
893
                                        // found resource in required module
894
                                        return result;
×
895
                                }
896
                        }
897
                }
898
                
899
                //look through the aware of modules.
900
                for (Module publicImport : awareOfModules) {
1✔
901
                        if (seenModules.contains(publicImport.getModuleId())) {
×
902
                                continue;
×
903
                        }
904
                        
905
                        ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport);
×
906
                        
907
                        if (mcl != null) {
×
908
                                result = mcl.findResource(name, requestor, seenModules);
×
909
                        }
910
                        
911
                        if (result != null) {
×
912
                                // found resource in aware of module
913
                                return result;
×
914
                        }
915
                }
916
                
917
                return result;
1✔
918
        }
919
        
920
        /**
921
         * Find all occurrences of a resource (image, file, etc) in the module structure
922
         *
923
         * @param result URL of the file found
924
         * @param name String path and name of the file to find
925
         * @param requestor ModuleClassLoader in which to start
926
         * @param seenModules Set&lt;String&gt; moduleIds that have been checked already
927
         * @throws IOException
928
         * @see #findResources(String)
929
         * @see #findResource(String, ModuleClassLoader, Set)
930
         */
931
        protected void findResources(final List<URL> result, final String name, final ModuleClassLoader requestor,
932
                Set<String> seenModules) throws IOException {
933
                if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) {
1✔
934
                        return;
×
935
                }
936
                for (Enumeration<URL> enm = super.findResources(name); enm.hasMoreElements();) {
1✔
937
                        URL url = enm.nextElement();
1✔
938
                        if (isResourceVisible(name, url, requestor)) {
1✔
939
                                result.add(url);
1✔
940
                        }
941
                }
1✔
942
                
943
                if (seenModules == null) {
1✔
944
                        seenModules = new HashSet<>();
1✔
945
                }
946
                seenModules.add(getModule().getModuleId());
1✔
947
                if (requiredModules != null) {
1✔
948
                        for (Module publicImport : requiredModules) {
1✔
949
                                if (seenModules.contains(publicImport.getModuleId())) {
1✔
950
                                        continue;
×
951
                                }
952
                                
953
                                ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport);
1✔
954
                                
955
                                if (mcl != null) {
1✔
956
                                        mcl.findResources(result, name, requestor, seenModules);
1✔
957
                                }
958
                        }
959
                }
960
                
961
                //look through the aware of modules.
962
                for (Module publicImport : awareOfModules) {
1✔
963
                        if (seenModules.contains(publicImport.getModuleId())) {
×
964
                                continue;
×
965
                        }
966
                        
967
                        ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport);
×
968
                        
969
                        if (mcl != null) {
×
970
                                mcl.findResources(result, name, requestor, seenModules);
×
971
                        }
972
                }
973
        }
1✔
974
        
975
        /**
976
         * Check if the given resource (image, file, etc) is visible by this classloader
977
         *
978
         * @param name String path and name to check
979
         * @param url URL of the library file
980
         * @param requestor ModuleClassLoader in which to look
981
         * @return true/false whether this resource is visibile by this classloader
982
         */
983
        protected boolean isResourceVisible(final String name, final URL url, final ModuleClassLoader requestor) {
984
                if (this == requestor) {
1✔
985
                        return true;
1✔
986
                }
987
                try {
988
                        String file = url.getFile();
1✔
989
                        new URL(url.getProtocol(), url.getHost(), file.substring(0, file.length() - name.length()));
1✔
990
                }
991
                catch (MalformedURLException mue) {
×
992
                        log.error("can't get resource library URL", mue);
×
993
                        return false;
×
994
                }
1✔
995
                
996
                return true;
1✔
997
        }
998
        
999
        /**
1000
         * Expands the URL into the temporary folder if the URL points to a resource inside of a jar
1001
         * file
1002
         *
1003
         * @param result
1004
         * @return URL to the expanded result or null if an error occurred
1005
         */
1006
        private URL expandIfNecessary(URL result) {
1007
                if (result == null || !"jar".equals(result.getProtocol())) {
1✔
1008
                        return result;
1✔
1009
                }
1010
                
1011
                File tmpFolder = getLibCacheFolderForModule(module);
1✔
1012
                
1013
                return OpenmrsClassLoader.expandURL(result, tmpFolder);
1✔
1014
        }
1015
        
1016
        /**
1017
         * Contains all class packages provided by the module, including those contained in jars.
1018
         * <p>
1019
         * It is used by {@link OpenmrsClassLoader#loadClass(String, boolean)} and in particular
1020
         * {@link ModuleFactory#getModuleClassLoadersForPackage(String)} to quickly find possible
1021
         * loaders for the given class. Although it takes some time to extract all provided packages
1022
         * from a module, it pays off when loading classes. It is much faster to query a map of packages
1023
         * than iterating over all class loaders to find which one to use.
1024
         * 
1025
         * @return the provided packages
1026
         */
1027
        public Set<String> getProvidedPackages() {
1028
                return providedPackages;
1✔
1029
        }
1030
        
1031
        /**
1032
         * @see java.lang.Object#toString()
1033
         */
1034
        @Override
1035
        public String toString() {
1036
                return "{ModuleClassLoader: uid=" + System.identityHashCode(this) + "; " + module + "}";
×
1037
        }
1038
        
1039
}
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