• 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

41.83
/api/src/main/java/org/openmrs/util/OpenmrsClassLoader.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.util;
11

12
import java.io.File;
13
import java.io.IOException;
14
import java.io.InputStream;
15
import java.lang.ref.WeakReference;
16
import java.lang.reflect.Field;
17
import java.lang.reflect.Modifier;
18
import java.net.URI;
19
import java.net.URISyntaxException;
20
import java.net.URL;
21
import java.net.URLClassLoader;
22
import java.net.URLConnection;
23
import java.sql.Driver;
24
import java.sql.DriverManager;
25
import java.sql.SQLException;
26
import java.util.ArrayList;
27
import java.util.Collections;
28
import java.util.Enumeration;
29
import java.util.HashSet;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Set;
33
import java.util.WeakHashMap;
34
import java.util.concurrent.ConcurrentHashMap;
35

36
import org.apache.commons.lang3.StringUtils;
37
import org.openmrs.api.APIException;
38
import org.openmrs.api.context.Context;
39
import org.openmrs.module.ModuleClassLoader;
40
import org.openmrs.module.ModuleFactory;
41
import org.openmrs.module.ModuleUtil;
42
import org.openmrs.scheduler.SchedulerException;
43
import org.openmrs.scheduler.SchedulerService;
44
import org.slf4j.Logger;
45
import org.slf4j.LoggerFactory;
46

47
/**
48
 * This classloader knows about the current ModuleClassLoaders and will attempt to load classes from
49
 * them if needed
50
 */
51
public class OpenmrsClassLoader extends URLClassLoader {
52
        
53
        private static Logger log = LoggerFactory.getLogger(OpenmrsClassLoader.class);
1✔
54
        
55
        private static volatile File libCacheFolder;
56
        
57
        private static final Object libCacheFolderLock = new Object();
1✔
58
        
59
        // placeholder to hold mementos to restore
60
        private static Map<String, OpenmrsMemento> mementos = new WeakHashMap<>();
1✔
61
        
62
        /**
63
         * Holds all classes that has been requested from this class loader. We use weak references so that
64
         * module classes can be garbage collected when modules are unloaded.
65
         */
66
        private Map<String, WeakReference<Class<?>>> cachedClasses = new ConcurrentHashMap<>();
1✔
67
        
68
        // suffix of the OpenMRS required library cache folder
69
        private static final String LIBCACHESUFFIX = ".openmrs-lib-cache";
70
        
71
        /**
72
         * Creates the instance for the OpenmrsClassLoader
73
         */
74
        public OpenmrsClassLoader(ClassLoader parent) {
75
                super(new URL[0], parent);
1✔
76
                
77
                if (parent instanceof OpenmrsClassLoader) {
1✔
78
                        throw new IllegalArgumentException("Parent must not be OpenmrsClassLoader nor null");
×
79
                } else if (parent instanceof ModuleClassLoader) {
1✔
80
                        throw new IllegalArgumentException("Parent must not be ModuleClassLoader");
×
81
                }
82
                
83
                OpenmrsClassLoaderHolder.INSTANCE = this;
1✔
84
                
85
                log.debug("Creating new OpenmrsClassLoader instance with parent: {}", parent);
1✔
86
                
87
                //disable caching so the jars aren't locked
88
                //if performance is effected, this can be disabled in favor of
89
                //copying all opened jars to a temp location
90
                //(ala org.apache.catalina.loader.WebappClassLoader antijarlocking)
91
                URLConnection urlConnection = new OpenmrsURLConnection();
1✔
92
                urlConnection.setDefaultUseCaches(false);
1✔
93
        }
1✔
94
        
95
        /**
96
         * Normal constructor. Sets this class as the parent classloader
97
         */
98
        public OpenmrsClassLoader() {
99
                this(OpenmrsClassLoader.class.getClassLoader());
1✔
100
        }
1✔
101
        
102
        /**
103
         * Private class to hold the one classloader used throughout openmrs. This is an alternative to
104
         * storing the instance object on {@link OpenmrsClassLoader} itself so that garbage collection
105
         * can happen correctly.
106
         */
107
        private static class OpenmrsClassLoaderHolder {
108

109
                private OpenmrsClassLoaderHolder() {
110
                }
111
                
112
                private static OpenmrsClassLoader INSTANCE = null;
1✔
113
                
114
        }
115
        
116
        /**
117
         * Get the static/singular instance of the module class loader
118
         *
119
         * @return OpenmrsClassLoader
120
         */
121
        public static OpenmrsClassLoader getInstance() {
122
                if (OpenmrsClassLoaderHolder.INSTANCE == null) {
1✔
123
                        OpenmrsClassLoaderHolder.INSTANCE = new OpenmrsClassLoader();
1✔
124
                }
125
                
126
                return OpenmrsClassLoaderHolder.INSTANCE;
1✔
127
        }
128
        
129
        /**
130
         * It attempts to load classes from the module class loaders first and then
131
         * tries the web container class loader (parent class loader).
132
         * 
133
         * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
134
         * <strong>Should</strong> load class from cache second time
135
         * <strong>Should</strong> not load class from cache if class loader has been disposed
136
         * <strong>Should</strong> load class from parent first
137
         * <strong>Should</strong> load class if two module class loaders have same packages
138
         */
139
        @Override
140
        public synchronized Class<?> loadClass(String name, final boolean resolve) throws ClassNotFoundException {
141
                // Check if the class has already been requested from this class loader
142
                Class<?> c = getCachedClass(name);
1✔
143
                if (c == null) {
1✔
144
                        // We do not try to load classes using this.findClass on purpose.
145
                        // All classes are loaded by web container or by module class loaders.
146
                        
147
                        // First try loading from modules such that we allow modules to load
148
                        // different versions of the same libraries that may already be used
149
                        // by core or the web container. An example is the chartsearch module
150
                        // which uses different versions of lucene and solr from core
151
                        String packageName = StringUtils.substringBeforeLast(name, ".");
1✔
152
                        Set<ModuleClassLoader> moduleClassLoaders = ModuleFactory.getModuleClassLoadersForPackage(packageName);
1✔
153
                        for (ModuleClassLoader moduleClassLoader : moduleClassLoaders) {
1✔
154
                                try {
155
                                        c = moduleClassLoader.loadClass(name);
1✔
156
                                        break;
1✔
157
                                }
158
                                catch (ClassNotFoundException e) {
1✔
159
                                        // Continue trying...
160
                                }
161
                        }
1✔
162
                        
163
                        if (c == null) {
1✔
164
                                // Finally try loading from web container
165
                                c = getParent().loadClass(name);
1✔
166
                        }
167
                        
168
                        cacheClass(name, c);
1✔
169
                }
170
                
171
                if (resolve) {
1✔
172
                        resolveClass(c);
×
173
                }
174
                
175
                return c;
1✔
176
        }
177
        
178
        private Class<?> getCachedClass(String name) {
179
                WeakReference<Class<?>> ref = cachedClasses.get(name);
1✔
180
                if (ref != null) {
1✔
181
                        Class<?> loadedClass = ref.get();
1✔
182
                        if (loadedClass == null || loadedClass.getClassLoader() == null) {
1✔
183
                                // Class has been garbage collected
184
                                cachedClasses.remove(name);
1✔
185
                                loadedClass = null;
1✔
186
                        } else if (loadedClass.getClassLoader() instanceof ModuleClassLoader) {
1✔
187
                                ModuleClassLoader moduleClassLoader = (ModuleClassLoader) loadedClass.getClassLoader();
1✔
188
                                if (moduleClassLoader.isDisposed()) {
1✔
189
                                        // Class has been unloaded
190
                                        cachedClasses.remove(name);
1✔
191
                                        loadedClass = null;
1✔
192
                                }
193
                        }
194
                        
195
                        return loadedClass;
1✔
196
                }
197
                return null;
1✔
198
        }
199
        
200
        private void cacheClass(String name, Class<?> clazz) {
201
                cachedClasses.put(name, new WeakReference<>(clazz));
1✔
202
        }
1✔
203
        
204
        /**
205
         * @see java.net.URLClassLoader#findResource(java.lang.String)
206
         */
207
        @Override
208
        public URL findResource(final String name) {
209
                log.trace("finding resource: {}", name);
1✔
210
                
211
                URL result;
212
                for (ModuleClassLoader classLoader : ModuleFactory.getModuleClassLoaders()) {
1✔
213
                        result = classLoader.findResource(name);
1✔
214
                        if (result != null) {
1✔
215
                                return result;
×
216
                        }
217
                }
1✔
218
                
219
                // look for the resource in the parent
220
                result = super.findResource(name);
1✔
221
                
222
                // expand the jar url if necessary
223
                if (result != null && "jar".equals(result.getProtocol()) && name.contains("openmrs")) {
1✔
224
                        result = expandURL(result, getLibCacheFolder());
×
225
                }
226
                
227
                return result;
1✔
228
        }
229
        
230
        /**
231
         * @see java.net.URLClassLoader#findResources(java.lang.String)
232
         */
233
        @Override
234
        public Enumeration<URL> findResources(final String name) throws IOException {
235
                Set<URI> results = new HashSet<>();
1✔
236
                for (ModuleClassLoader classLoader : ModuleFactory.getModuleClassLoaders()) {
1✔
237
                        Enumeration<URL> urls = classLoader.findResources(name);
1✔
238
                        while (urls.hasMoreElements()) {
1✔
239
                                URL result = urls.nextElement();
1✔
240
                                if (result != null) {
1✔
241
                                        try {
242
                                                results.add(result.toURI());
1✔
243
                                        }
244
                                        catch (URISyntaxException e) {
×
245
                                                throwInvalidURI(result, e);
×
246
                                        }
1✔
247
                                }
248
                        }
1✔
249
                }
1✔
250
                
251
                for (Enumeration<URL> en = super.findResources(name); en.hasMoreElements();) {
1✔
252
                        URL url = en.nextElement();
×
253
                        try {
254
                                results.add(url.toURI());
×
255
                        }
256
                        catch (URISyntaxException e) {
×
257
                                throwInvalidURI(url, e);
×
258
                        }
×
259
                }
×
260
                
261
                List<URL> resources = new ArrayList<>(results.size());
1✔
262
                for (URI result : results) {
1✔
263
                        resources.add(result.toURL());
1✔
264
                }
1✔
265
                
266
                return Collections.enumeration(resources);
1✔
267
        }
268
        
269
        private void throwInvalidURI(URL url, Exception e) throws IOException {
270
                throw new IOException(url.getPath() + " is not a valid URI", e);
×
271
        }
272
        
273
        /**
274
         * Searches all known module classloaders first, then parent classloaders
275
         *
276
         * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
277
         */
278
        @Override
279
        public InputStream getResourceAsStream(String file) {
280
                for (ModuleClassLoader classLoader : ModuleFactory.getModuleClassLoaders()) {
1✔
281
                        InputStream result = classLoader.getResourceAsStream(file);
1✔
282
                        if (result != null) {
1✔
283
                                return result;
1✔
284
                        }
285
                }
1✔
286
                
287
                return super.getResourceAsStream(file);
1✔
288
        }
289
        
290
        /**
291
         * Searches all known module classloaders first, then parent classloaders
292
         *
293
         * @see java.lang.ClassLoader#getResources(java.lang.String)
294
         */
295
        @Override
296
        public Enumeration<URL> getResources(String packageName) throws IOException {
297
                Set<URI> results = new HashSet<>();
1✔
298
                for (ModuleClassLoader classLoader : ModuleFactory.getModuleClassLoaders()) {
1✔
299
                        Enumeration<URL> urls = classLoader.getResources(packageName);
1✔
300
                        while (urls.hasMoreElements()) {
1✔
301
                                URL result = urls.nextElement();
1✔
302
                                if (result != null) {
1✔
303
                                        try {
304
                                                results.add(result.toURI());
1✔
305
                                        }
306
                                        catch (URISyntaxException e) {
×
307
                                                throwInvalidURI(result, e);
×
308
                                        }
1✔
309
                                }
310
                        }
1✔
311
                }
1✔
312
                
313
                for (Enumeration<URL> en = super.getResources(packageName); en.hasMoreElements();) {
1✔
314
                        URL url = en.nextElement();
1✔
315
                        try {
316
                                results.add(url.toURI());
1✔
317
                        }
318
                        catch (URISyntaxException e) {
×
319
                                throwInvalidURI(url, e);
×
320
                        }
1✔
321
                }
1✔
322
                
323
                List<URL> resources = new ArrayList<>(results.size());
1✔
324
                for (URI result : results) {
1✔
325
                        resources.add(result.toURL());
1✔
326
                }
1✔
327
                
328
                return Collections.enumeration(resources);
1✔
329
        }
330
        
331
        /**
332
         * @see java.lang.Object#toString()
333
         */
334
        @Override
335
        public String toString() {
336
                return "Openmrs" + super.toString();
×
337
        }
338
        
339
        /**
340
         * Destroy the current instance of the classloader. Note**: After calling this and after the new
341
         * service is set up, All classes using this instance should be flushed. This would allow all
342
         * java classes that were loaded by the old instance variable to be gc'd and modules to load in
343
         * new java classes
344
         *
345
         * @see #flushInstance()
346
         */
347
        public static void destroyInstance() {
348
                
349
                // remove all thread references to this class
350
                // Walk up all the way to the root thread group
351
                ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
×
352
                ThreadGroup parent;
353
                while ((parent = rootGroup.getParent()) != null) {
×
354
                        rootGroup = parent;
×
355
                }
356
                
357
                log.info("this classloader hashcode: {}", OpenmrsClassLoaderHolder.INSTANCE.hashCode());
×
358
                
359
                OpenmrsClassScanner.destroyInstance();
×
360
                
361
                OpenmrsClassLoaderHolder.INSTANCE = null;
×
362
        }
×
363
        
364
        /**
365
         * Sets the class loader, for all threads referencing a destroyed openmrs class loader, 
366
         * to the current one.
367
         */
368
        public static void setThreadsToNewClassLoader() {
369
                //Give ownership of all threads loaded by the old class loader to the new one.
370
                //Examples of such threads are: Keep-Alive-Timer, MySQL Statement Cancellation Timer, etc
371
                //That way they will no longer hold onto the destroyed OpenmrsClassLoader and hence
372
                //allow it to be garbage collected after a spring application context refresh, when a new one is created.
373
                Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
×
374
                Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
×
375
                for (Thread thread : threadArray) {
×
376
                        
377
                        ClassLoader classLoader = thread.getContextClassLoader();
×
378
                        
379
                        //Some threads have a null class loader reference. e.g Finalizer, Reference Handler, etc
380
                        if (classLoader == null) {
×
381
                                continue;
×
382
                        }
383
                        
384
                        //Threads referencing the current class loader are good.
385
                        if (classLoader == getInstance()) {
×
386
                                continue;
×
387
                        }
388
                        
389
                        //For threads referencing any destroyed class loader, point them to the new one.
390
                        if (classLoader instanceof OpenmrsClassLoader) {
×
391
                                thread.setContextClassLoader(getInstance());
×
392
                        }
393
                }
394
        }
×
395
        
396
        // List all threads and recursively list all subgroup
397
        private static List<Thread> listThreads(ThreadGroup group, String indent) {
398
                List<Thread> threadToReturn = new ArrayList<>();
×
399
                
400
                log.error(indent + "Group[" + group.getName() + ":" + group.getClass() + "]");
×
401
                int nt = group.activeCount();
×
402
                Thread[] threads = new Thread[nt * 2 + 10]; //nt is not accurate
×
403
                nt = group.enumerate(threads, false);
×
404
                
405
                // List every thread in the group
406
                for (int i = 0; i < nt; i++) {
×
407
                        Thread t = threads[i];
×
408
                        log.error(indent
×
409
                                + "  Thread["
410
                                + t.getName()
×
411
                                + ":"
412
                                + t.getClass()
×
413
                                + ":"
414
                                + (t.getContextClassLoader() == null ? "null cl" : t.getContextClassLoader().getClass().getName() + " "
×
415
                                        + t.getContextClassLoader().hashCode()) + "]");
×
416
                        threadToReturn.add(t);
×
417
                }
418
                
419
                // Recursively list all subgroups
420
                int ng = group.activeGroupCount();
×
421
                ThreadGroup[] groups = new ThreadGroup[ng * 2 + 10];
×
422
                ng = group.enumerate(groups, false);
×
423
                
424
                for (int i = 0; i < ng; i++) {
×
425
                        threadToReturn.addAll(listThreads(groups[i], indent + "  "));
×
426
                }
427
                
428
                return threadToReturn;
×
429
        }
430
        
431
        public static void onShutdown() {
432
                
433
                //Since we are shutting down, stop all threads that reference the openmrs class loader.
434
                Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
×
435
                Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
×
436
                for (Thread thread : threadArray) {
×
437
                        
438
                        ClassLoader classLoader = thread.getContextClassLoader();
×
439
                        
440
                        //Threads like Finalizer, Reference Handler, etc have null class loader reference.
441
                        if (classLoader == null) {
×
442
                                continue;
×
443
                        }
444
                        
445
                        if (classLoader instanceof OpenmrsClassLoader) {
×
446
                                try {
447
                                        //Set to WebappClassLoader just in case stopping fails.
448
                                        thread.setContextClassLoader(classLoader.getParent());
×
449
                                        
450
                                        //Stopping the current thread will halt all current cleanup.
451
                                        //So do not ever ever even attempt stopping it. :)
452
                                        if (thread == Thread.currentThread()) {
×
453
                                                continue;
×
454
                                        }
455
                                        
456
                                        log.info("onShutdown Stopping thread: {}", thread.getName());
×
457
                                        thread.stop();
×
458
                                }
459
                                catch (Exception ex) {
×
460
                                        log.error(ex.getMessage(), ex);
×
461
                                }
×
462
                        }
463
                }
464
                
465
                clearReferences();
×
466
        }
×
467
        
468
        /**
469
         * This clears any references this classloader might have that will prevent garbage collection. <br>
470
         * <br>
471
         * Borrowed from Tomcat's WebappClassLoader#clearReferences() (not javadoc linked intentionally) <br>
472
         * The only difference between this and Tomcat's implementation is that this one only acts on
473
         * openmrs objects and also clears out static java.* packages. Tomcat acts on all objects and
474
         * does not clear our static java.* objects.
475
         *
476
         * @since 1.5
477
         */
478
        protected static void clearReferences() {
479
                
480
                // Unregister any JDBC drivers loaded by this classloader
481
                Enumeration<Driver> drivers = DriverManager.getDrivers();
×
482
                while (drivers.hasMoreElements()) {
×
483
                        Driver driver = drivers.nextElement();
×
484
                        if (driver.getClass().getClassLoader() == getInstance()) {
×
485
                                try {
486
                                        DriverManager.deregisterDriver(driver);
×
487
                                }
488
                                catch (SQLException e) {
×
489
                                        log.warn("SQL driver deregistration failed", e);
×
490
                                }
×
491
                        }
492
                }
×
493
                
494
                // Null out any static or final fields from loaded classes,
495
                // as a workaround for apparent garbage collection bugs
496
                for (WeakReference<Class<?>> refClazz : getInstance().cachedClasses.values()) {
×
497
                        if (refClazz == null) {
×
498
                                continue;
×
499
                        }
500
                        Class<?> clazz = refClazz.get();
×
501
                        if (clazz != null && clazz.getName().contains("openmrs")) { // only clean up openmrs classes
×
502
                                try {
503
                                        Field[] fields = clazz.getDeclaredFields();
×
504
                                        for (Field field : fields) {
×
505
                                                int mods = field.getModifiers();
×
506
                                                if (field.getType().isPrimitive() || (field.getName().contains("$"))) {
×
507
                                                        continue;
×
508
                                                }
509
                                                if (Modifier.isStatic(mods)) {
×
510
                                                        try {
511
                                                                // do not clear the log field on this class yet
512
                                                                if (clazz.equals(OpenmrsClassLoader.class) && "log".equals(field.getName())) {
×
513
                                                                        continue;
×
514
                                                                }
515
                                                                field.setAccessible(true);
×
516
                                                                if (Modifier.isFinal(mods)) {
×
517
                                                                        if (!(field.getType().getName().startsWith("javax."))) {
×
518
                                                                                nullInstance(field.get(null));
×
519
                                                                        }
520
                                                                } else {
521
                                                                        field.set(null, null);
×
522
                                                                        log.debug("Set field {} to null in class {}", field.getName(), clazz.getName());
×
523
                                                                }
524
                                                        }
525
                                                        catch (Exception t) {
×
526
                                                                log.debug("Could not set field {} to null in class {}", field.getName(), clazz.getName(), t);
×
527
                                                        }
×
528
                                                }
529
                                        }
530
                                }
531
                                catch (Exception t) {
×
532
                                        log.debug("Could not clean fields for class {}", clazz.getName(), t);
×
533
                                }
×
534
                        }
535
                }
×
536
                
537
                // now we can clear the log field on this class
538
                OpenmrsClassLoader.log = null;
×
539
                
540
                getInstance().cachedClasses.clear();
×
541
        }
×
542
        
543
        /**
544
         * Used by {@link #clearReferences()} upon application close. <br>
545
         * <br>
546
         * Borrowed from Tomcat's WebappClassLoader.
547
         *
548
         * @param instance the object whose fields need to be nulled out
549
         */
550
        protected static void nullInstance(Object instance) {
551
                if (instance == null) {
×
552
                        return;
×
553
                }
554
                Field[] fields = instance.getClass().getDeclaredFields();
×
555
                for (Field field : fields) {
×
556
                        int mods = field.getModifiers();
×
557
                        if (field.getType().isPrimitive() || (field.getName().contains("$"))) {
×
558
                                continue;
×
559
                        }
560
                        try {
561
                                field.setAccessible(true);
×
562
                                if (!(Modifier.isStatic(mods) && Modifier.isFinal(mods))) {
×
563
                                        Object value = field.get(instance);
×
564
                                        if (null != value) {
×
565
                                                Class<?> valueClass = value.getClass();
×
566
                                                if (!loadedByThisOrChild(valueClass)) {
×
567
                                                        log.debug(
×
568
                                                            "Not setting field {} to null in object of class {} because the referenced object was of type {} which was not loaded by this WebappClassLoader.",
569
                                                            field.getName(), instance.getClass().getName(), valueClass.getName());
×
570
                                                } else {
571
                                                        field.set(instance, null);
×
572
                                                        log.debug("Set field {} to null in class {}", field.getName(), instance.getClass().getName());
×
573
                                                }
574
                                        }
575
                                } 
576
                        }
577
                        catch (Exception e) {
×
578
                                log.debug("Could not set field {} to null in object instance of class {}", field.getName(),
×
579
                                    instance.getClass().getName(), e);
×
580
                        }
×
581
                }
582
        }
×
583
        
584
        /**
585
         * Determine whether a class was loaded by this class loader or one of its child class loaders. <br>
586
         * <br>
587
         * Borrowed from Tomcat's WebappClassLoader
588
         */
589
        protected static boolean loadedByThisOrChild(Class<?> clazz) {
590
                boolean result = false;
×
591
                for (ClassLoader classLoader = clazz.getClassLoader(); null != classLoader; classLoader = classLoader.getParent()) {
×
592
                        if (classLoader.equals(getInstance())) {
×
593
                                result = true;
×
594
                                break;
×
595
                        }
596
                }
597
                return result;
×
598
        }
599
        
600
        /**
601
         * This method should be called before destroying the instance
602
         *
603
         * @see #destroyInstance()
604
         */
605
        public static void saveState() {
606
                try {
607
                        String key = SchedulerService.class.getName();
×
608
                        if (!Context.isRefreshingContext()) {
×
609
                                mementos.put(key, Context.getSchedulerService().saveToMemento());
×
610
                        }
611
                }
612
                catch (Exception t) {
×
613
                        // pass
614
                }
×
615
        }
×
616
        
617
        /**
618
         * This method should be called after restoring the instance
619
         *
620
         * @see #destroyInstance()
621
         * @see #saveState()
622
         */
623
        public static void restoreState() {
624
                try {
625
                        String key = SchedulerService.class.getName();
×
626
                        Context.getSchedulerService().restoreFromMemento(mementos.get(key));
×
627
                }
628
                catch (APIException e) {
×
629
                        // pass
630
                }
×
631
                mementos.clear();
×
632
        }
×
633
        
634
        /**
635
         * All objects depending on the old classloader should be restarted here Should be called after
636
         * destoryInstance() and after the service is restarted
637
         *
638
         * @see #destroyInstance()
639
         */
640
        public static void flushInstance() {
641
                try {
642
                        SchedulerService service = null;
×
643
                        try {
644
                                service = Context.getSchedulerService();
×
645
                        }
646
                        catch (APIException e2) {
×
647
                                // if there isn't a scheduler service yet, ignore error
648
                                log.warn("Unable to get scheduler service", e2);
×
649
                        }
×
650
                        if (service != null) {
×
651
                                service.rescheduleAllTasks();
×
652
                        }
653
                }
654
                catch (SchedulerException e) {
×
655
                        log.error("Failed to restart scheduler tasks", e);
×
656
                }
×
657
        }
×
658
        
659
        /**
660
         * Get the temporary "work" directory for expanded jar files
661
         * <p>
662
         * If the <code>optimized.startup</code> runtime property is set to <code>false</code>,
663
         * the cache folder is deleted upon startup, if it exists.
664
         *
665
         * @return temporary location for storing the libraries
666
         */
667
        public static File getLibCacheFolder() {
668
                // Cache the location for all calls until OpenMRS is restarted
669
                if (libCacheFolder != null) {
1✔
670
                        return libCacheFolder;
1✔
671
                }
672
                
673
                synchronized (libCacheFolderLock) {
1✔
674
                        if (libCacheFolder != null) {
1✔
NEW
675
                                return libCacheFolder;
×
676
                        }
677
                        
678
                        File newLibCacheFolder = new File(OpenmrsUtil.getApplicationDataDirectory(), LIBCACHESUFFIX);
1✔
679
                        
680
                        log.debug("Libraries cache folder is {}", newLibCacheFolder);
1✔
681
                        
682
                        if (newLibCacheFolder.exists()) {
1✔
NEW
683
                                if (Context.isOptimizedStartup()) {
×
NEW
684
                                        log.debug("Optimized startup enabled, using lib cache folder from last run {}", newLibCacheFolder);
×
685
                                } else {
NEW
686
                                        log.debug("Optimized startup disabled, cleaning up lib cache folder {}", newLibCacheFolder);
×
687
                                        try {
NEW
688
                                                OpenmrsUtil.deleteDirectory(newLibCacheFolder);
×
689

NEW
690
                                                newLibCacheFolder.mkdirs();
×
NEW
691
                                        } catch (IOException io) {
×
NEW
692
                                                log.warn("Unable to delete: {}", newLibCacheFolder.getName());
×
NEW
693
                                        }
×
694
                                }
695
                        } else {
696
                                // otherwise just create the dir structure
697
                                newLibCacheFolder.mkdirs();
1✔
698
                        }
699
                        
700
                        libCacheFolder = newLibCacheFolder;
1✔
701
                }
1✔
702
                
703
                return libCacheFolder;
1✔
704
        }
705
        
706
        /**
707
         * Expand the given URL into the given folder
708
         *
709
         * @param result URL of the file to expand
710
         * @param folder File (directory) to place the expanded file
711
         * @return the URL at the expanded location
712
         */
713
        public static URL expandURL(URL result, File folder) {
714
                String extForm = result.toExternalForm();
1✔
715
                // trim out "jar:file:/ and ascii spaces"
716
                if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {
1✔
717
                        extForm = extForm.replaceFirst("jar:file:", "").replaceAll("%20", " ");
1✔
718
                } else {
719
                        extForm = extForm.replaceFirst("jar:file:/", "").replaceAll("%20", " ");
×
720
                }
721
                
722
                log.debug("url external form: {}", extForm);
1✔
723
                
724
                int i = extForm.indexOf("!");
1✔
725
                String jarPath = extForm.substring(0, i);
1✔
726
                String filePath = extForm.substring(i + 2); // skip over both the '!' and the '/'
1✔
727
                
728
                log.debug("jarPath: {}", jarPath);
1✔
729
                log.debug("filePath: {}", filePath);
1✔
730
                
731
                File file = new File(folder, filePath);
1✔
732
                
733
                log.debug("absolute path: {}", file.getAbsolutePath());
1✔
734
                
735
                try {
736
                        // if the file has been expanded already, return that
737
                        if (file.exists()) {
1✔
738
                                return file.toURI().toURL();
1✔
739
                        } else {
740
                                // expand the url and return a url to the temp file
741
                                File jarFile = new File(jarPath);
1✔
742
                                if (!jarFile.exists()) {
1✔
743
                                        log.warn("Cannot find jar at: {} for url: {}", jarFile, result);
×
744
                                        return null;
×
745
                                }
746
                                
747
                                ModuleUtil.expandJar(jarFile, folder, filePath, true);
1✔
748
                                return file.toURI().toURL();
1✔
749
                        }
750
                }
751
                catch (IOException io) {
×
752
                        log.warn("Unable to expand url: {}", result, io);
×
753
                        return null;
×
754
                }
755
        }
756
        
757
        /**
758
         * This class exists solely so OpenmrsClassLoader can call the (should be static) method
759
         * <code>URLConnection.setDefaultUseCaches(Boolean)</code>. This causes jars opened to not be
760
         * locked (and allows for the webapp to be reloadable).
761
         */
762
        private class OpenmrsURLConnection extends URLConnection {
763
                
764
                public OpenmrsURLConnection() {
1✔
765
                        super(null);
1✔
766
                }
1✔
767
                
768
                @Override
769
                public void connect() throws IOException {
770
                        
771
                }
×
772
                
773
        }
774
}
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