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

openmrs / openmrs-core / 20784346756

07 Jan 2026 02:13PM UTC coverage: 63.283%. First build
20784346756

push

github

web-flow
TRUNK-6418 Run liquibase checks and data imports only when version of core or modules changes (#5603)

* TRUNK-6418: Run liquibase checks and data imports only when version of core or modules changes

(cherry picked from commit 4723e71c3)

* TRUNK-6418: Follow up adjustments

---------

Co-authored-by: IamMujuziMoses <mujuzimoses@gmail.com>

49 of 112 new or added lines in 9 files covered. (43.75%)

23080 of 36471 relevant lines covered (63.28%)

0.63 hits per line

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

14.1
/web/src/main/java/org/openmrs/web/filter/update/UpdateFilter.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.web.filter.update;
11

12
import liquibase.changelog.ChangeSet;
13
import liquibase.exception.LockException;
14
import org.apache.commons.collections.CollectionUtils;
15
import org.apache.commons.lang3.StringUtils;
16
import org.openmrs.liquibase.ChangeLogDetective;
17
import org.openmrs.util.DatabaseUpdateException;
18
import org.openmrs.util.DatabaseUpdater;
19
import org.openmrs.liquibase.ChangeSetExecutorCallback;
20
import org.openmrs.util.DatabaseUpdaterLiquibaseProvider;
21
import org.openmrs.util.InputRequiredException;
22
import org.openmrs.liquibase.ChangeLogVersionFinder;
23
import org.openmrs.util.OpenmrsThreadPoolHolder;
24
import org.openmrs.util.OpenmrsUtil;
25
import org.openmrs.util.RoleConstants;
26
import org.openmrs.util.Security;
27
import org.openmrs.web.Listener;
28
import org.openmrs.web.WebDaemon;
29
import org.openmrs.web.filter.StartupFilter;
30
import org.openmrs.web.filter.initialization.InitializationFilter;
31
import org.openmrs.web.filter.util.CustomResourceLoader;
32
import org.openmrs.web.filter.util.ErrorMessageConstants;
33
import org.openmrs.web.filter.util.FilterUtil;
34
import org.slf4j.Logger;
35
import org.slf4j.LoggerFactory;
36
import org.springframework.web.context.ContextLoader;
37

38
import javax.servlet.FilterChain;
39
import javax.servlet.FilterConfig;
40
import javax.servlet.ServletContext;
41
import javax.servlet.ServletContextEvent;
42
import javax.servlet.ServletException;
43
import javax.servlet.ServletRequest;
44
import javax.servlet.ServletResponse;
45
import javax.servlet.http.HttpServletRequest;
46
import javax.servlet.http.HttpServletResponse;
47
import java.io.IOException;
48
import java.sql.Connection;
49
import java.sql.PreparedStatement;
50
import java.sql.ResultSet;
51
import java.sql.SQLException;
52
import java.util.ArrayList;
53
import java.util.Arrays;
54
import java.util.HashMap;
55
import java.util.LinkedList;
56
import java.util.List;
57
import java.util.Locale;
58
import java.util.Map;
59
import java.util.concurrent.Future;
60

61
/**
62
 * This is the second filter that is processed. It is only active when OpenMRS has some liquibase
63
 * updates that need to be run. If updates are needed, this filter/wizard asks for a super user to
64
 * authenticate and review the updates before continuing.
65
 */
66
public class UpdateFilter extends StartupFilter {
1✔
67
        
68
        protected final Logger log = LoggerFactory.getLogger(UpdateFilter.class);
1✔
69
        
70
        /**
71
         * The velocity macro page to redirect to if an error occurs or on initial startup
72
         */
73
        private static final String DEFAULT_PAGE = "maintenance.vm";
74
        
75
        /**
76
         * The page that lists off all the currently unexecuted changes
77
         */
78
        private static final String REVIEW_CHANGES = "reviewchanges.vm";
79
        
80
        private static final String PROGRESS_VM_AJAXREQUEST = "updateProgress.vm.ajaxRequest";
81
        
82
        /**
83
         * The model object behind this set of screens
84
         */
85
        private UpdateFilterModel updateFilterModel = null;
1✔
86
        
87
        /**
88
         * Variable set as soon as the update is done or verified to not be needed so that future calls
89
         * through this filter are a simple boolean check
90
         */
91
        private static boolean updatesRequired = true;
1✔
92
        
93
        /**
94
         * Used on all pages after the first to make sure the user isn't trying to cheat and do some url
95
         * magic to hack in.
96
         */
97
        private volatile boolean authenticatedSuccessfully = false;
1✔
98
        
99
        private UpdateFilterCompletion updateJob;
100
        
101
        /**
102
         * Variable set to true as soon as the update begins and set to false when the process ends. This
103
         * thread should only be accesses through the synchronized method.
104
         */
105
        private static boolean isDatabaseUpdateInProgress = false;
1✔
106
        
107
        /**
108
         * Variable set to true when the db lock is released. It's needed to prevent repeatedly releasing
109
         * this lock by other threads. This var should only be accessed through the synchronized method.
110
         */
111
        private static Boolean lockReleased = false;
1✔
112
        
113
        /**
114
         * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on GET requests
115
         *
116
         * @param httpRequest
117
         * @param httpResponse
118
         */
119
        @Override
120
        protected void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
121
                throws IOException, ServletException {
122
                
123
                Map<String, Object> referenceMap = new HashMap<>();
×
124
                checkLocaleAttributesForFirstTime(httpRequest);
×
125
                // we need to save current user language in references map since it will be used when template
126
                // will be rendered
127
                if (httpRequest.getSession().getAttribute(FilterUtil.LOCALE_ATTRIBUTE) != null) {
×
128
                        referenceMap.put(FilterUtil.LOCALE_ATTRIBUTE,
×
129
                            httpRequest.getSession().getAttribute(FilterUtil.LOCALE_ATTRIBUTE));
×
130
                }
131
                // do step one of the wizard
132
                renderTemplate(DEFAULT_PAGE, referenceMap, httpResponse);
×
133
        }
×
134
        
135
        /**
136
         * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on POST requests
137
         */
138
        @Override
139
        protected synchronized void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
140
                throws IOException, ServletException {
141
                
142
                final String updJobStatus = "updateJobStarted";
×
143
                String page = httpRequest.getParameter("page");
×
144
                Map<String, Object> referenceMap = new HashMap<>();
×
145
                if (httpRequest.getSession().getAttribute(FilterUtil.LOCALE_ATTRIBUTE) != null) {
×
146
                        referenceMap.put(FilterUtil.LOCALE_ATTRIBUTE,
×
147
                            httpRequest.getSession().getAttribute(FilterUtil.LOCALE_ATTRIBUTE));
×
148
                }
149
                
150
                // step one
151
                if (DEFAULT_PAGE.equals(page)) {
×
152
                        
153
                        String username = httpRequest.getParameter("username");
×
154
                        String password = httpRequest.getParameter("password");
×
155
                        
156
                        log.debug("Attempting to authenticate user: " + username);
×
157
                        if (authenticateAsSuperUser(username, password)) {
×
158
                                log.debug("Authentication successful.  Redirecting to 'reviewupdates' page.");
×
159
                                // set a variable so we know that the user started here
160
                                authenticatedSuccessfully = true;
×
161
                                
162
                                //Set variable to tell us whether updates are already in progress
163
                                referenceMap.put("isDatabaseUpdateInProgress", isDatabaseUpdateInProgress);
×
164
                                
165
                                // if another super user has already launched database update
166
                                // allow current super user to review update progress
167
                                if (isDatabaseUpdateInProgress) {
×
168
                                        referenceMap.put(updJobStatus, true);
×
169
                                        httpResponse.setContentType("text/html");
×
170
                                        renderTemplate(REVIEW_CHANGES, referenceMap, httpResponse);
×
171
                                        return;
×
172
                                }
173
                                
174
                                // we will only get here if the db update is NOT running. 
175
                                // so if we find a db lock, we should release it because
176
                                // it was leftover from a previous db update crash
177
                                
178
                                if (!isLockReleased() && DatabaseUpdater.isLocked()) {
×
179
                                        // first we trying to release db lock if it exists
180
                                        try {
181
                                                DatabaseUpdater.releaseDatabaseLock();
×
182
                                                setLockReleased(true);
×
183
                                        }
184
                                        catch (LockException e) {
×
185
                                                // do nothing
186
                                        }
×
187
                                        // if lock was released successfully we need to get unrun changes
188
                                        updateFilterModel.updateChanges();
×
189
                                }
190
                                
191
                                // need to configure velocity tool box for using user's preferred locale
192
                                // so we should store it for further using when configuring velocity tool context
193
                                String localeParameter = FilterUtil.restoreLocale(username);
×
194
                                httpRequest.getSession().setAttribute(FilterUtil.LOCALE_ATTRIBUTE, localeParameter);
×
195
                                referenceMap.put(FilterUtil.LOCALE_ATTRIBUTE, localeParameter);
×
196
                                
197
                                renderTemplate(REVIEW_CHANGES, referenceMap, httpResponse);
×
198
                        } else {
×
199
                                // if not authenticated, show main page again
200
                                try {
201
                                        log.debug("Sleeping for 3 seconds because of a bad username/password");
×
202
                                        Thread.sleep(3000);
×
203
                                }
204
                                catch (InterruptedException e) {
×
205
                                        log.error("Unable to sleep", e);
×
206
                                        throw new ServletException("Got interrupted while trying to sleep thread", e);
×
207
                                }
×
208
                                errors.put(ErrorMessageConstants.UPDATE_ERROR_UNABLE_AUTHENTICATE, null);
×
209
                                renderTemplate(DEFAULT_PAGE, referenceMap, httpResponse);
×
210
                        }
211
                }
×
212
                // step two of wizard in case if there were some warnings
213
                else if (REVIEW_CHANGES.equals(page)) {
×
214
                        
215
                        if (!authenticatedSuccessfully) {
×
216
                                // throw the user back to the main page because they are cheating
217
                                renderTemplate(DEFAULT_PAGE, referenceMap, httpResponse);
×
218
                                return;
×
219
                        }
220
                        
221
                        //if no one has run any required updates
222
                        if (!isDatabaseUpdateInProgress) {
×
223
                                isDatabaseUpdateInProgress = true;
×
224
                                updateJob = new UpdateFilterCompletion();
×
225
                                updateJob.start();
×
226
                                
227
                                // allows current user see progress of running update
228
                                // and also will hide the "Run Updates" button
229
                                
230
                                referenceMap.put(updJobStatus, true);
×
231
                        } else {
232
                                referenceMap.put("isDatabaseUpdateInProgress", true);
×
233
                                // as well we need to allow current user to
234
                                // see progress of already started updates
235
                                // and also will hide the "Run Updates" button
236
                                referenceMap.put(updJobStatus, true);
×
237
                        }
238
                        
239
                        renderTemplate(REVIEW_CHANGES, referenceMap, httpResponse);
×
240
                        
241
                } else if (PROGRESS_VM_AJAXREQUEST.equals(page)) {
×
242
                        
243
                        httpResponse.setContentType("text/json");
×
244
                        httpResponse.setHeader("Cache-Control", "no-cache");
×
245
                        Map<String, Object> result = new HashMap<>();
×
246
                        if (updateJob != null) {
×
247
                                result.put("hasErrors", updateJob.hasErrors());
×
248
                                if (updateJob.hasErrors()) {
×
249
                                        errors.putAll(updateJob.getErrors());
×
250
                                }
251
                                
252
                                if (updateJob.hasWarnings() && updateJob.getExecutingChangesetId() == null) {
×
253
                                        result.put("hasWarnings", updateJob.hasWarnings());
×
254
                                        StringBuilder sb = new StringBuilder("<ul>");
×
255
                                        
256
                                        for (String warning : updateJob.getUpdateWarnings()) {
×
257
                                                sb.append("<li>").append(warning).append("</li>");
×
258
                                        }
×
259
                                        
260
                                        sb.append("</ul>");
×
261
                                        result.put("updateWarnings", sb.toString());
×
262
                                        result.put("updateLogFile",
×
263
                                            StringUtils.replace(
×
264
                                                OpenmrsUtil.getApplicationDataDirectory() + DatabaseUpdater.DATABASE_UPDATES_LOG_FILE, "\\",
×
265
                                                "\\\\"));
266
                                        updateJob.hasUpdateWarnings = false;
×
267
                                        updateJob.getUpdateWarnings().clear();
×
268
                                }
269
                                
270
                                result.put("updatesRequired", updatesRequired());
×
271
                                result.put("message", updateJob.getMessage());
×
272
                                result.put("changesetIds", updateJob.getChangesetIds());
×
273
                                result.put("executingChangesetId", updateJob.getExecutingChangesetId());
×
274
                                
275
                                addLogLinesToResponse(result);
×
276
                        }
277
                        
278
                        String jsonText = toJSONString(result);
×
279
                        httpResponse.getWriter().write(jsonText);
×
280
                }
281
        }
×
282
        
283
        /**
284
         * It sets locale attribute for current session when user is making first GET http request to
285
         * application. It retrieves user locale from request object and checks if this locale is supported
286
         * by application. If not, it tries to load system default locale. If it's not specified it uses
287
         * {@link Locale#ENGLISH} by default
288
         *
289
         * @param httpRequest the http request object
290
         */
291
        public void checkLocaleAttributesForFirstTime(HttpServletRequest httpRequest) {
292
                Locale locale = httpRequest.getLocale();
×
293
                String systemDefaultLocale = FilterUtil.readSystemDefaultLocale(null);
×
294
                if (CustomResourceLoader.getInstance(httpRequest).getAvailablelocales().contains(locale)) {
×
295
                        httpRequest.getSession().setAttribute(FilterUtil.LOCALE_ATTRIBUTE, locale.toString());
×
296
                        log.info("Used client's locale " + locale.toString());
×
297
                } else if (StringUtils.isNotBlank(systemDefaultLocale)) {
×
298
                        httpRequest.getSession().setAttribute(FilterUtil.LOCALE_ATTRIBUTE, systemDefaultLocale);
×
299
                        log.info("Used system default locale " + systemDefaultLocale);
×
300
                } else {
301
                        httpRequest.getSession().setAttribute(FilterUtil.LOCALE_ATTRIBUTE, Locale.ENGLISH.toString());
×
302
                        log.info("Used default locale " + Locale.ENGLISH.toString());
×
303
                }
304
        }
×
305
        
306
        /**
307
         * Look in the users table for a user with this username and password and see if they have a role of
308
         * {@link RoleConstants#SUPERUSER}.
309
         *
310
         * @param usernameOrSystemId user entered username
311
         * @param password user entered password
312
         * @return true if this user has the super user role
313
         * @see #isSuperUser(Connection, Integer) <strong>Should</strong> return false if given invalid
314
         *      credentials <strong>Should</strong> return false if given user is not superuser
315
         *      <strong>Should</strong> return true if given user is superuser <strong>Should</strong> not
316
         *      authorize retired superusers <strong>Should</strong> authenticate with systemId
317
         */
318
        protected boolean authenticateAsSuperUser(String usernameOrSystemId, String password) throws ServletException {
319
                Connection connection = null;
1✔
320
                try {
321
                        connection = DatabaseUpdater.getConnection();
1✔
322
                        
323
                        String select = "select user_id, password, salt from users where (username = ? or system_id = ?) and retired = '0'";
1✔
324
                        PreparedStatement statement = null;
1✔
325
                        try {
326
                                statement = connection.prepareStatement(select);
1✔
327
                                statement.setString(1, usernameOrSystemId);
1✔
328
                                statement.setString(2, usernameOrSystemId);
1✔
329
                                
330
                                if (statement.execute()) {
1✔
331
                                        ResultSet results = null;
1✔
332
                                        try {
333
                                                results = statement.getResultSet();
1✔
334
                                                if (results.next()) {
1✔
335
                                                        Integer userId = results.getInt(1);
1✔
336
                                                        DatabaseUpdater.setAuthenticatedUserId(userId);
1✔
337
                                                        String storedPassword = results.getString(2);
1✔
338
                                                        String salt = results.getString(3);
1✔
339
                                                        String passwordToHash = password + salt;
1✔
340
                                                        return Security.hashMatches(storedPassword, passwordToHash) && isSuperUser(connection, userId);
1✔
341
                                                }
342
                                        }
343
                                        finally {
344
                                                if (results != null) {
1✔
345
                                                        try {
346
                                                                results.close();
1✔
347
                                                        }
348
                                                        catch (Exception resultsCloseEx) {
×
349
                                                                log.error("Failed to quietly close ResultSet", resultsCloseEx);
×
350
                                                        }
1✔
351
                                                }
352
                                        }
353
                                }
354
                        }
355
                        finally {
356
                                if (statement != null) {
1✔
357
                                        try {
358
                                                statement.close();
1✔
359
                                        }
360
                                        catch (Exception statementCloseEx) {
×
361
                                                log.error("Failed to quietly close Statement", statementCloseEx);
×
362
                                        }
1✔
363
                                }
364
                        }
365
                }
366
                catch (Exception connectionEx) {
×
367
                        log.error(
×
368
                            "Error while trying to authenticate as super user. Ignore this if you are upgrading from OpenMRS 1.5 to 1.6",
369
                            connectionEx);
370
                        
371
                        // we may not have upgraded User to have retired instead of voided yet, so if the query above fails, we try
372
                        // again the old way
373
                        if (connection != null) {
×
374
                                String select = "select user_id, password, salt from users where (username = ? or system_id = ?) and voided = '0'";
×
375
                                PreparedStatement statement = null;
×
376
                                try {
377
                                        statement = connection.prepareStatement(select);
×
378
                                        statement.setString(1, usernameOrSystemId);
×
379
                                        statement.setString(2, usernameOrSystemId);
×
380
                                        if (statement.execute()) {
×
381
                                                ResultSet results = null;
×
382
                                                try {
383
                                                        results = statement.getResultSet();
×
384
                                                        if (results.next()) {
×
385
                                                                Integer userId = results.getInt(1);
×
386
                                                                DatabaseUpdater.setAuthenticatedUserId(userId);
×
387
                                                                String storedPassword = results.getString(2);
×
388
                                                                String salt = results.getString(3);
×
389
                                                                String passwordToHash = password + salt;
×
390
                                                                return Security.hashMatches(storedPassword, passwordToHash)
×
391
                                                                        && isSuperUser(connection, userId);
×
392
                                                        }
393
                                                }
394
                                                finally {
395
                                                        if (results != null) {
×
396
                                                                try {
397
                                                                        results.close();
×
398
                                                                }
399
                                                                catch (Exception resultsCloseEx) {
×
400
                                                                        log.error("Failed to quietly close ResultSet", resultsCloseEx);
×
401
                                                                }
×
402
                                                        }
403
                                                }
404
                                        }
405
                                }
406
                                catch (Exception unhandeledEx) {
×
407
                                        log.error("Error while trying to authenticate as super user (voided version)", unhandeledEx);
×
408
                                }
409
                                finally {
410
                                        if (statement != null) {
×
411
                                                try {
412
                                                        statement.close();
×
413
                                                }
414
                                                catch (Exception statementCloseEx) {
×
415
                                                        log.error("Failed to quietly close Statement", statementCloseEx);
×
416
                                                }
×
417
                                        }
418
                                }
419
                        }
420
                }
421
                finally {
422
                        if (connection != null) {
1✔
423
                                try {
424
                                        connection.close();
1✔
425
                                }
426
                                catch (SQLException e) {
×
427
                                        log.debug("Error while closing the database", e);
×
428
                                }
1✔
429
                        }
430
                }
431
                
432
                return false;
1✔
433
        }
434
        
435
        /**
436
         * Checks the given user to see if they have been given the {@link RoleConstants#SUPERUSER}
437
         * role. This method does not look at child roles.
438
         *
439
         * @param connection the java sql connection to use
440
         * @param userId the user id to look at
441
         * @return true if the given user is a super user
442
         * @throws SQLException <strong>Should</strong> return true if given user has superuser role
443
         *             <strong>Should</strong> return false if given user does not have the super user role
444
         */
445
        protected boolean isSuperUser(Connection connection, Integer userId) throws SQLException {
446
                // the 'Administrator' part of this string is necessary because if the database was upgraded
447
                // by OpenMRS 1.6 alpha then System Developer was renamed to that. This has to be here so we
448
                // can roll back that change in 1.6 beta+
449
                String select = "select 1 from user_role where user_id = ? and (role = ? or role = 'Administrator')";
1✔
450
                PreparedStatement statement = connection.prepareStatement(select);
1✔
451
                statement.setInt(1, userId);
1✔
452
                statement.setString(2, RoleConstants.SUPERUSER);
1✔
453
                if (statement.execute()) {
1✔
454
                        ResultSet results = statement.getResultSet();
1✔
455
                        if (results.next()) {
1✔
456
                                return results.getInt(1) == 1;
1✔
457
                        }
458
                }
459
                
460
                return false;
1✔
461
        }
462
        
463
        /**
464
         * `` Do everything to get openmrs going.
465
         *
466
         * @param servletContext the servletContext from the filterconfig
467
         * @see Listener#startOpenmrs(ServletContext)
468
         */
469
        private void startOpenmrs(ServletContext servletContext) throws Exception {
470
                // start spring
471
                // after this point, all errors need to also call: contextLoader.closeWebApplicationContext(event.getServletContext())
472
                // logic copied from org.springframework.web.context.ContextLoaderListener
473
                ContextLoader contextLoader = new ContextLoader();
×
474
                contextLoader.initWebApplicationContext(servletContext);
×
475
                
476
                try {
477
                        WebDaemon.startOpenmrs(servletContext);
×
478
                }
479
                catch (Exception exception) {
×
480
                        contextLoader.closeWebApplicationContext(servletContext);
×
481
                        throw exception;
×
482
                }
×
483
        }
×
484
        
485
        /**
486
         * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
487
         */
488
        @Override
489
        public void init(FilterConfig filterConfig) throws ServletException {
490
                super.init(filterConfig);
×
491
                
492
                log.debug("Initializing the UpdateFilter");
×
493
                
494
                if (!InitializationFilter.initializationRequired()
×
495
                        || (Listener.isSetupNeeded() && Listener.runtimePropertiesFound())) {
×
496
                        updateFilterModel = new UpdateFilterModel();
×
497
                        /*
498
                         * In this case, Listener#runtimePropertiesFound == true and InitializationFilter Wizard is skipped,
499
                         * so no need to reset Context's RuntimeProperties again, because of Listener.contextInitialized has set it.
500
                         */
501
                        try {
502
                                // this pings the DatabaseUpdater.updatesRequired which also
503
                                // considers a db lock to be a 'required update'
504
                                if (updateFilterModel.updateRequired) {
×
505
                                        setUpdatesRequired(true);
×
506
                                } else if (updateFilterModel.changes == null) {
×
507
                                        setUpdatesRequired(false);
×
508
                                } else {
509
                                        log.debug("Setting updates required to {} because of the size of unrun changes", (!updateFilterModel.changes.isEmpty()));
×
510
                                        setUpdatesRequired(!updateFilterModel.changes.isEmpty());
×
511
                                }
512
                        }
513
                        catch (Exception e) {
×
514
                                throw new ServletException("Unable to determine if updates are required", e);
×
515
                        }
×
516
                } else {
517
                        /*
518
                         * The initialization wizard will update the database to the latest version, so the user will not need any updates here.
519
                         * See end of InitializationFilter#InitializationCompletion
520
                         */
521
                        log.debug(
×
522
                            "Setting updates required to false because the user doesn't have any runtime properties yet or database is empty");
523
                        setUpdatesRequired(false);
×
524
                }
525
        }
×
526
        
527
        /**
528
         * @see org.openmrs.web.filter.StartupFilter#getUpdateFilterModel()
529
         */
530
        @Override
531
        protected Object getUpdateFilterModel() {
532
                // this object was initialized in the #init(FilterConfig) method
533
                return updateFilterModel;
×
534
        }
535
        
536
        /**
537
         * @see org.openmrs.web.filter.StartupFilter#skipFilter(HttpServletRequest)
538
         */
539
        @Override
540
        public boolean skipFilter(HttpServletRequest httpRequest) {
541
                return !PROGRESS_VM_AJAXREQUEST.equals(httpRequest.getParameter("page")) && !updatesRequired();
×
542
        }
543
        
544
        /**
545
         * Used by the Listener to know if this filter wants to do its magic
546
         *
547
         * @return true if updates have been determined to be required
548
         * @see #init(FilterConfig)
549
         * @see Listener#isSetupNeeded()
550
         * @see Listener#contextInitialized(ServletContextEvent)
551
         */
552
        public static synchronized boolean updatesRequired() {
553
                return updatesRequired;
×
554
        }
555
        
556
        /**
557
         * @param updatesRequired the updatesRequired to set
558
         */
559
        public static synchronized void setUpdatesRequired(boolean updatesRequired) {
560
                UpdateFilter.updatesRequired = updatesRequired;
×
561
        }
×
562
        
563
        /**
564
         * Indicates if database lock was released. It will also used to prevent releasing existing lock of
565
         * liquibasechangeloglock table by another user, when he also tries to run database update when
566
         * another user is currently running it
567
         */
568
        public static Boolean isLockReleased() {
569
                return lockReleased;
×
570
        }
571
        
572
        public static synchronized void setLockReleased(Boolean lockReleased) {
573
                UpdateFilter.lockReleased = lockReleased;
×
574
        }
×
575
        
576
        /**
577
         * @see org.openmrs.web.filter.StartupFilter#getTemplatePrefix()
578
         */
579
        @Override
580
        protected String getTemplatePrefix() {
581
                return "org/openmrs/web/filter/update/";
×
582
        }
583
        
584
        /**
585
         * This class controls the final steps and is used by the ajax calls to know what updates have been
586
         * executed. TODO: Break this out into a separate (non-inner) class
587
         */
588
        private class UpdateFilterCompletion {
589

590
                private Runnable r;
591

592
                private String executingChangesetId = null;
×
593
                
594
                private List<String> changesetIds = new ArrayList<>();
×
595
                
596
                private Map<String, Object[]> errors = new HashMap<>();
×
597
                
598
                private String message = null;
×
599
                
600
                private boolean erroneous = false;
×
601
                
602
                private boolean hasUpdateWarnings = false;
×
603
                
604
                private List<String> updateWarnings = new LinkedList<>();
×
605
                
606
                public synchronized void reportError(String error, Object... params) {
607
                        Map<String, Object[]> reportedErrors = new HashMap<>();
×
608
                        reportedErrors.put(error, params);
×
609
                        reportErrors(reportedErrors);
×
610
                }
×
611
                
612
                public synchronized void reportErrors(Map<String, Object[]> errs) {
613
                        errors.putAll(errs);
×
614
                        erroneous = true;
×
615
                }
×
616
                
617
                public synchronized boolean hasErrors() {
618
                        return erroneous;
×
619
                }
620
                
621
                public synchronized Map<String, Object[]> getErrors() {
622
                        return errors;
×
623
                }
624
                
625
                /**
626
                 * Start the completion stage. This fires up the thread to do all the work.
627
                 */
628
                public void start() {
629
                        setUpdatesRequired(true);
×
630
                        OpenmrsThreadPoolHolder.threadExecutor.submit(r);
×
631
                }
×
632
                
633
                public synchronized void setMessage(String message) {
634
                        this.message = message;
×
635
                }
×
636
                
637
                public synchronized String getMessage() {
638
                        return message;
×
639
                }
640
                
641
                public synchronized void addChangesetId(String changesetid) {
642
                        this.changesetIds.add(changesetid);
×
643
                        this.executingChangesetId = changesetid;
×
644
                }
×
645
                
646
                public synchronized List<String> getChangesetIds() {
647
                        return changesetIds;
×
648
                }
649
                
650
                public synchronized String getExecutingChangesetId() {
651
                        return executingChangesetId;
×
652
                }
653
                
654
                /**
655
                 * @return the database updater Warnings
656
                 */
657
                public synchronized List<String> getUpdateWarnings() {
658
                        return updateWarnings;
×
659
                }
660
                
661
                public synchronized boolean hasWarnings() {
662
                        return hasUpdateWarnings;
×
663
                }
664
                
665
                public synchronized void reportWarnings(List<String> warnings) {
666
                        updateWarnings.addAll(warnings);
×
667
                        hasUpdateWarnings = true;
×
668
                }
×
669
                
670
                /**
671
                 * This class does all the work of creating the desired database, user, updates, etc
672
                 */
673
                public UpdateFilterCompletion() {
×
674
                         r = new Runnable() {
×
675
                                
676
                                /**
677
                                 * TODO split this up into multiple testable methods
678
                                 *
679
                                 * @see java.lang.Runnable#run()
680
                                 */
681
                                @Override
682
                                public void run() {
683
                                        try {
684
                                                /**
685
                                                 * A callback class that prints out info about liquibase changesets
686
                                                 */
687
                                                class PrintingChangeSetExecutorCallback implements ChangeSetExecutorCallback {
688
                                                        
689
                                                        private String message;
690
                                                        
691
                                                        public PrintingChangeSetExecutorCallback(String message) {
×
692
                                                                this.message = message;
×
693
                                                        }
×
694
                                                        
695
                                                        /**
696
                                                         * @see ChangeSetExecutorCallback#executing(liquibase.changelog.ChangeSet, int)
697
                                                         */
698
                                                        @Override
699
                                                        public void executing(ChangeSet changeSet, int numChangeSetsToRun) {
700
                                                                addChangesetId(changeSet.getId());
×
701
                                                                setMessage(message);
×
702
                                                        }
×
703
                                                        
704
                                                }
705

706
                                                
707
                                                try {
NEW
708
                                                        if (DatabaseUpdater.updatesRequired()) {
×
NEW
709
                                                                setMessage("Updating the database to the latest version");
×
710

NEW
711
                                                                ChangeLogDetective changeLogDetective = ChangeLogDetective.getInstance();
×
NEW
712
                                                                ChangeLogVersionFinder changeLogVersionFinder = new ChangeLogVersionFinder();
×
713

NEW
714
                                                                List<String> changelogs = new ArrayList<>();
×
NEW
715
                                                                List<String> warnings = new ArrayList<>();
×
716

NEW
717
                                                                String version = changeLogDetective.getInitialLiquibaseSnapshotVersion(DatabaseUpdater.CONTEXT,
×
718
                                                                        new DatabaseUpdaterLiquibaseProvider());
719

NEW
720
                                                                log.debug(
×
721
                                                                        "updating the database with versions of liquibase-update-to-latest files greater than '{}'",
722
                                                                        version);
723

NEW
724
                                                                changelogs.addAll(changeLogVersionFinder
×
NEW
725
                                                                        .getUpdateFileNames(changeLogVersionFinder.getUpdateVersionsGreaterThan(version)));
×
726

NEW
727
                                                                log.debug("found applicable Liquibase update change logs: {}", changelogs);
×
728

NEW
729
                                                                for (String changelog : changelogs) {
×
NEW
730
                                                                        log.debug("applying Liquibase changelog '{}'", changelog);
×
731

NEW
732
                                                                        List<String> currentWarnings = DatabaseUpdater.executeChangelog(changelog,
×
733
                                                                                new PrintingChangeSetExecutorCallback("executing Liquibase changelog :" + changelog));
734

NEW
735
                                                                        if (currentWarnings != null) {
×
NEW
736
                                                                                warnings.addAll(currentWarnings);
×
737
                                                                        }
NEW
738
                                                                }
×
NEW
739
                                                                executingChangesetId = null; // clear out the last changeset
×
740

NEW
741
                                                                if (CollectionUtils.isNotEmpty(warnings)) {
×
NEW
742
                                                                        reportWarnings(warnings);
×
743
                                                                }
744
                                                        }
745
                                                }
746
                                                catch (InputRequiredException inputRequired) {
×
747
                                                        // the user would be stepped through the questions returned here.
748
                                                        log.error("Not implemented", inputRequired);
×
749
                                                        updateFilterModel.updateChanges();
×
750
                                                        reportError(ErrorMessageConstants.UPDATE_ERROR_INPUT_NOT_IMPLEMENTED,
×
751
                                                            inputRequired.getMessage());
×
752
                                                        return;
×
753
                                                }
754
                                                catch (DatabaseUpdateException e) {
×
755
                                                        log.error("Unable to update the database", e);
×
756
                                                        Map<String, Object[]> databaseUpdateErrors = new HashMap<>();
×
757
                                                        databaseUpdateErrors.put(ErrorMessageConstants.UPDATE_ERROR_UNABLE, null);
×
758
                                                        for (String errorMessage : Arrays.asList(e.getMessage().split("\n"))) {
×
759
                                                                databaseUpdateErrors.put(errorMessage, null);
×
760
                                                        }
×
761
                                                        updateFilterModel.updateChanges();
×
762
                                                        reportErrors(databaseUpdateErrors);
×
763
                                                        return;
×
764
                                                }
765
                                                catch (Exception e) {
×
766
                                                        log.error("Unable to update the database", e);
×
767
                                                        return;
×
768
                                                }
×
769
                                                
770
                                                setMessage("Starting OpenMRS");
×
771
                                                try {
772
                                                        startOpenmrs(filterConfig.getServletContext());
×
773
                                                }
774
                                                catch (Exception e) {
×
775
                                                        log.error("Unable to complete the startup.", e);
×
776
                                                        reportError(ErrorMessageConstants.UPDATE_ERROR_COMPLETE_STARTUP, e.getMessage());
×
777
                                                        return;
×
778
                                                }
×
779
                                                
780
                                                // set this so that the wizard isn't run again on next page load
781
                                                setUpdatesRequired(false);
×
782
                                        }
783
                                        finally {
784
                                                if (!hasErrors()) {
×
785
                                                        setUpdatesRequired(false);
×
786
                                                }
787
                                                //reset to let other user's make requests after updates are run
788
                                                isDatabaseUpdateInProgress = false;
×
789
                                        }
790
                                }
×
791
                        };
792
                }
×
793
        }
794
}
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