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

openmrs / openmrs-core / 21949914059

12 Feb 2026 02:07PM UTC coverage: 65.405% (-0.01%) from 65.419%
21949914059

push

github

chibongho
TRUNK-6541 prevent ValidateUtil.validate() from throwing UnexpectedRo… (#5775)

* TRUNK-6541 prevent ValidateUtil.validate() from throwing UnexpectedRollbackException

* maven(deps): bump org.apache.lucene:lucene-analysis-phonetic (#5773)

Bumps org.apache.lucene:lucene-analysis-phonetic from 9.12.3 to 10.3.2.

---
updated-dependencies:
- dependency-name: org.apache.lucene:lucene-analysis-phonetic
  dependency-version: 10.3.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Refactor to provide a BOM project (#5760)

* add comment

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ian <52504170+ibacher@users.noreply.github.com>

13 of 18 new or added lines in 1 file covered. (72.22%)

5 existing lines in 3 files now uncovered.

23797 of 36384 relevant lines covered (65.41%)

0.65 hits per line

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

86.54
/api/src/main/java/org/openmrs/validator/ValidateUtil.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.validator;
11

12
import java.util.LinkedHashSet;
13
import java.util.Set;
14

15
import org.apache.commons.lang3.StringUtils;
16
import org.openmrs.OpenmrsObject;
17
import org.openmrs.api.ValidationException;
18
import org.openmrs.api.context.Context;
19
import org.openmrs.api.db.hibernate.HibernateUtil;
20
import org.springframework.transaction.UnexpectedRollbackException;
21
import org.springframework.util.Assert;
22
import org.springframework.validation.BindException;
23
import org.springframework.validation.Errors;
24
import org.springframework.validation.FieldError;
25
import org.springframework.validation.ObjectError;
26

27
/**
28
 * This class should be used in the *Services to validate objects before saving them. <br>
29
 * <br>
30
 * The validators are added to this class in the spring applicationContext-service.xml file. <br>
31
 * <br>
32
 * Example usage:
33
 *
34
 * <pre>
35
 *  public Order saveOrder(order) {
36
 *          ValidateUtil.validate(order);
37
 *          dao.saveOrder(order);
38
 *  }
39
 * </pre>
40
 *
41
 * @since 1.5
42
 */
43
public class ValidateUtil {
44

45
        private ValidateUtil() {
46
        }
47

48
        /**
49
         * This is set in {@link Context#checkCoreDataset()} class
50
         */
51
        private static Boolean disableValidation = false;
1✔
52
        
53
        /** This enables consuming code to disable validation if needed for specific operations in the current thread */
54
        private static final ThreadLocal<Boolean> disableValidationForThread = new ThreadLocal<>();
1✔
55
        
56
        /**
57
         * Test the given object against all validators that are registered as compatible with the
58
         * object class
59
         *
60
         * @param obj the object to validate
61
         * @throws ValidationException thrown if a binding exception occurs
62
         * <strong>Should</strong> throw APIException if errors occur during validation
63
         * <strong>Should</strong> return immediately if validation is disabled
64
         */
65
        public static void validate(Object obj) throws ValidationException {
66
                if (disableValidation || isValidationDisabledForThread()) {
1✔
67
                        return;
1✔
68
                }
69

70
                obj = HibernateUtil.getRealObjectFromProxy(obj);
1✔
71
                
72
                Errors errors = new BindException(obj, "");
1✔
73
                
74
                try {
75
                        Context.getAdministrationService().validate(obj, errors);
1✔
NEW
76
                } catch (UnexpectedRollbackException ex) {
×
77
                        // We shouldn't be committing to the DB here, but for some reason, it's possible for a
78
                        // UnexpectedRollbackException to get thrown if there are errors.
79
                        // If that's the case, throw a more informative ValidationException instead.
80
                        // See: https://openmrs.atlassian.net/browse/TRUNK-6541 
NEW
81
                        if (errors.hasErrors()) {
×
NEW
82
                                throwValidationError(errors, obj);
×
83
                        } else {
NEW
84
                                throw ex;
×
85
                        }
86
                }
1✔
87

88
                if (errors.hasErrors()) {
1✔
NEW
89
                        throwValidationError(errors, obj);
×
90
                } 
91
        }
1✔
92

93
        private static void throwValidationError(Errors errors, Object obj) {
94
                Set<String> uniqueErrorMessages = new LinkedHashSet<>();
1✔
95
                for (Object objerr : errors.getAllErrors()) {
1✔
96
                        ObjectError error = (ObjectError) objerr;
1✔
97
                        String message = Context.getMessageSourceService().getMessage(error.getCode(), error.getArguments(), Context.getLocale());
1✔
98
                        if (error instanceof FieldError) {
1✔
99
                                message = ((FieldError) error).getField() + ": " + message;
1✔
100
                        }
101
                        uniqueErrorMessages.add(message);
1✔
102
                }
1✔
103
                
104
                String exceptionMessage = "'" + obj + "' failed to validate with reason: ";
1✔
105
                exceptionMessage += StringUtils.join(uniqueErrorMessages, ", ");
1✔
106
                throw new ValidationException(exceptionMessage, errors);
1✔
107
        }
108
        
109
        /**
110
         * Test the given object against all validators that are registered as compatible with the
111
         * object class
112
         *
113
         * @param obj the object to validate
114
         * @param errors the validation errors found
115
         * @since 1.9
116
         * <strong>Should</strong> populate errors if object invalid
117
         * <strong>Should</strong> return immediately if validation is disabled and have no errors
118
         */
119
        public static void validate(Object obj, Errors errors) {
120
                if (disableValidation) {
1✔
121
                        return;
1✔
122
                }
123

124
                obj = HibernateUtil.getRealObjectFromProxy(obj);
1✔
125
                
126
                Context.getAdministrationService().validate(obj, errors);
1✔
127
        }
1✔
128
        
129
        /**
130
         * Test the field lengths are valid
131
         *
132
         * @param errors
133
         * @param aClass the class of the object being tested
134
         * @param fields a var args that contains all of the fields from the model
135
         * <strong>Should</strong> pass validation if regEx field length is not too long
136
         * <strong>Should</strong> fail validation if regEx field length is too long
137
         * <strong>Should</strong> fail validation if name field length is too long
138
         * <strong>Should</strong> return immediately if validation is disabled and have no errors
139
         */
140
        public static void validateFieldLengths(Errors errors, Class<?> aClass, String... fields) {
141
                if (disableValidation) {
1✔
142
                        return;
1✔
143
                }
144

145
                Assert.notNull(errors, "Errors object must not be null");
1✔
146
                for (String field : fields) {
1✔
147
                        Object value = errors.getFieldValue(field);
1✔
148
                        if (value == null || !(value instanceof String)) {
1✔
149
                                continue;
×
150
                        }
151
                        int length = Context.getAdministrationService().getMaximumPropertyLength((Class<? extends OpenmrsObject>) aClass, field);
1✔
152
                        if (length == -1) {
1✔
153
                                return;
×
154
                        }
155
                        if (((String) value).length() > length) {
1✔
156
                                errors.rejectValue(field, "error.exceededMaxLengthOfField", new Object[] { length }, null);
1✔
157
                        }
158
                }
159
        }
1✔
160

161
        public static Boolean getDisableValidation() {
162
                return disableValidation;
1✔
163
        }
164

165
        public static void setDisableValidation(Boolean disableValidation) {
166
                ValidateUtil.disableValidation = disableValidation;
1✔
167
        }
1✔
168

169
        /**
170
         * @return true if validation has been disabled for the current thread, false otherwise
171
         * @since 2.5.8
172
         */
173
        public static boolean isValidationDisabledForThread() {
174
                return disableValidationForThread.get() == Boolean.TRUE;
1✔
175
        }
176

177
        /**
178
         * Used to indicate that validation should be disabled for the current thread
179
         * NOTE: This should always be used in conjunction with the resumeValidationForThread method
180
         * @since 2.5.8
181
         */
182
        public static void disableValidationForThread() {
183
                disableValidationForThread.set(Boolean.TRUE);
1✔
184
        }
1✔
185

186
        /**
187
         * Used to indicate that validation should be re-enabled for the current thread
188
         * Typically this would be placed in a `finally` block after the disableValidationForThread method is used
189
         * @since 2.5.8
190
         */
191
        public static void resumeValidationForThread() {
192
                disableValidationForThread.remove();
1✔
193
        }
1✔
194
}
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