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

openmrs / openmrs-core / 10689050349

03 Sep 2024 07:05PM CUT coverage: 64.741% (+0.008%) from 64.733%
10689050349

push

github

web-flow
maven(deps): bump org.codehaus.mojo:buildnumber-maven-plugin (#4738)

Bumps [org.codehaus.mojo:buildnumber-maven-plugin](https://github.com/mojohaus/buildnumber-maven-plugin) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/mojohaus/buildnumber-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/buildnumber-maven-plugin/compare/3.2.0...3.2.1)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:buildnumber-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

22963 of 35469 relevant lines covered (64.74%)

0.65 hits per line

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

85.11
/api/src/main/java/org/openmrs/validator/ConceptValidator.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.Collection;
13
import java.util.HashSet;
14
import java.util.Locale;
15
import java.util.Set;
16

17
import org.apache.commons.collections.CollectionUtils;
18
import org.apache.commons.lang3.StringUtils;
19
import org.openmrs.Concept;
20
import org.openmrs.ConceptAnswer;
21
import org.openmrs.ConceptMap;
22
import org.openmrs.ConceptName;
23
import org.openmrs.annotation.Handler;
24
import org.openmrs.api.APIException;
25
import org.openmrs.api.DuplicateConceptNameException;
26
import org.openmrs.api.context.Context;
27
import org.slf4j.Logger;
28
import org.slf4j.LoggerFactory;
29
import org.springframework.validation.Errors;
30
import org.springframework.validation.ValidationUtils;
31
import org.springframework.validation.Validator;
32

33
/**
34
 * Validates {@link Concept} objects. <br>
35
 * These validations are also documented at <a
36
 * href="https://wiki.openmrs.org/x/-gkdAg">https://wiki.openmrs.org/x/-gkdAg</a>. Any changes made
37
 * to this source also need to be reflected on that page.
38
 */
39
@Handler(supports = { Concept.class }, order = 50)
40
public class ConceptValidator extends BaseCustomizableValidator implements Validator {
1✔
41
        
42
        // Logger for this class
43
        private static final Logger log = LoggerFactory.getLogger(ConceptValidator.class);
1✔
44
        
45
        /**
46
         * Determines if the command object being submitted is a valid type
47
         *
48
         * @see org.springframework.validation.Validator#supports(java.lang.Class)
49
         */
50
        @Override
51
        public boolean supports(Class<?> c) {
52
                return Concept.class.isAssignableFrom(c);
1✔
53
        }
54
        
55
        /**
56
         * Checks that a given concept object is valid.
57
         *
58
         * @see org.springframework.validation.Validator#validate(java.lang.Object,
59
         *      org.springframework.validation.Errors)
60
         * <strong>Should</strong> pass if the concept has at least one fully specified name added to it
61
         * <strong>Should</strong> fail if there is a duplicate unretired concept name in the locale
62
         * <strong>Should</strong> fail if there is a duplicate unretired preferred name in the same locale
63
         * <strong>Should</strong> fail if there is a duplicate unretired fully specified name in the same locale
64
         * <strong>Should</strong> fail if any names in the same locale for this concept are similar
65
         * <strong>Should</strong> pass if the concept with a duplicate name is retired
66
         * <strong>Should</strong> pass if the concept being validated is retired and has a duplicate name
67
         * <strong>Should</strong> fail if any name is an empty string
68
         * <strong>Should</strong> fail if the object parameter is null
69
         * <strong>Should</strong> pass if the concept is being updated with no name change
70
         * <strong>Should</strong> fail if any name is a null value
71
         * <strong>Should</strong> not allow multiple preferred names in a given locale
72
         * <strong>Should</strong> not allow multiple fully specified conceptNames in a given locale
73
         * <strong>Should</strong> not allow multiple short names in a given locale
74
         * <strong>Should</strong> not allow an index term to be a locale preferred name
75
         * <strong>Should</strong> fail if there is no name explicitly marked as fully specified
76
         * <strong>Should</strong> pass if the duplicate ConceptName is neither preferred nor fully Specified
77
         * <strong>Should</strong> pass if the concept has a synonym that is also a short name
78
         * <strong>Should</strong> fail if a term is mapped multiple times to the same concept
79
         * <strong>Should</strong> not fail if a term has two new mappings on it
80
         * <strong>Should</strong> fail if there is a duplicate unretired concept name in the same locale different than
81
         *         the system locale
82
         * <strong>Should</strong> pass for a new concept with a map created with deprecated concept map methods
83
         * <strong>Should</strong> pass for an edited concept with a map created with deprecated concept map methods
84
         * <strong>Should</strong> pass validation if field lengths are correct
85
         * <strong>Should</strong> fail validation if field lengths are not correct
86
         * <strong>Should</strong> pass if fully specified name is the same as short name
87
         * <strong>Should</strong> pass if different concepts have the same short name
88
         * <strong>Should</strong> fail if the concept datatype is null
89
         * <strong>Should</strong> fail if the concept class is null
90
         * <strong>Should</strong> pass if the concept is retired and the only validation failures would be in ConceptName 
91
         * or ConceptMap, as a retired Concept bypasses ConceptName and ConceptMap validation.
92
         */
93
        @Override
94
        public void validate(Object obj, Errors errors) throws APIException, DuplicateConceptNameException {
95
                
96
                if (obj == null || !(obj instanceof Concept)) {
1✔
97
                        throw new IllegalArgumentException("The parameter obj should not be null and must be of type" + Concept.class);
1✔
98
                }
99
                
100
                Concept conceptToValidate = (Concept) obj;
1✔
101
                //no name to validate, but why is this the case?
102
                if (conceptToValidate.getNames().isEmpty()) {
1✔
103
                        errors.reject("Concept.name.atLeastOneRequired");
1✔
104
                        return;
1✔
105
                }
106

107
                ValidationUtils.rejectIfEmpty(errors, "datatype", "Concept.datatype.empty");
1✔
108
                ValidationUtils.rejectIfEmpty(errors, "conceptClass", "Concept.conceptClass.empty");
1✔
109
                
110
                boolean hasFullySpecifiedName = false;
1✔
111
                for (Locale conceptNameLocale : conceptToValidate.getAllConceptNameLocales()) {
1✔
112
                        if (conceptToValidate.getRetired()) {
1✔
113
                                continue;
1✔
114
                        }
115
                        
116
                        boolean fullySpecifiedNameForLocaleFound = false;
1✔
117
                        boolean preferredNameForLocaleFound = false;
1✔
118
                        boolean shortNameForLocaleFound = false;
1✔
119
                        Set<String> validNamesFoundInLocale = new HashSet<>();
1✔
120
                        Collection<ConceptName> namesInLocale = conceptToValidate.getNames(conceptNameLocale);
1✔
121
                        for (ConceptName nameInLocale : namesInLocale) {
1✔
122
                                if (StringUtils.isBlank(nameInLocale.getName())) {
1✔
123
                                        log.debug("Name in locale '" + conceptNameLocale.toString()
1✔
124
                                                + "' cannot be an empty string or white space");
125
                                        errors.reject("Concept.name.empty");
1✔
126
                                }
127
                                if (nameInLocale.getLocalePreferred() != null) {
1✔
128
                                        if (nameInLocale.getLocalePreferred() && !preferredNameForLocaleFound) {
1✔
129
                                                if (nameInLocale.isIndexTerm()) {
1✔
130
                                                        log.warn("Preferred name in locale '" + conceptNameLocale.toString()
×
131
                                                                + "' shouldn't be an index term");
132
                                                        errors.reject("Concept.error.preferredName.is.indexTerm");
×
133
                                                } else if (nameInLocale.isShort()) {
1✔
134
                                                        log.warn("Preferred name in locale '" + conceptNameLocale.toString()
×
135
                                                                + "' shouldn't be a short name");
136
                                                        errors.reject("Concept.error.preferredName.is.shortName");
×
137
                                                } else if (nameInLocale.getVoided()) {
1✔
138
                                                        log.warn("Preferred name in locale '" + conceptNameLocale.toString()
×
139
                                                                + "' shouldn't be a voided name");
140
                                                        errors.reject("Concept.error.preferredName.is.voided");
×
141
                                                }
142
                                                
143
                                                preferredNameForLocaleFound = true;
1✔
144
                                        }
145
                                        //should have one preferred name per locale
146
                                        else if (nameInLocale.getLocalePreferred() && preferredNameForLocaleFound) {
1✔
147
                                                log.warn("Found multiple preferred names in locale '" + conceptNameLocale.toString() + "'");
×
148
                                                errors.reject("Concept.error.multipleLocalePreferredNames");
×
149
                                        }
150
                                }
151
                                
152
                                if (nameInLocale.isFullySpecifiedName()) {
1✔
153
                                        if (!hasFullySpecifiedName) {
1✔
154
                                                hasFullySpecifiedName = true;
1✔
155
                                        }
156
                                        if (!fullySpecifiedNameForLocaleFound) {
1✔
157
                                                fullySpecifiedNameForLocaleFound = true;
1✔
158
                                        } else {
159
                                                log.warn("Found multiple fully specified names in locale '" + conceptNameLocale.toString() + "'");
×
160
                                                errors.reject("Concept.error.multipleFullySpecifiedNames");
×
161
                                        }
162
                                        if (nameInLocale.getVoided()) {
1✔
163
                                                log.warn("Fully Specified name in locale '" + conceptNameLocale.toString()
×
164
                                                        + "' shouldn't be a voided name");
165
                                                errors.reject("Concept.error.fullySpecifiedName.is.voided");
×
166
                                        }
167
                                }
168
                                
169
                                if (nameInLocale.isShort()) {
1✔
170
                                        if (!shortNameForLocaleFound) {
1✔
171
                                                shortNameForLocaleFound = true;
1✔
172
                                        }
173
                                        //should have one short name per locale
174
                                        else {
175
                                                log.warn("Found multiple short names in locale '" + conceptNameLocale.toString() + "'");
×
176
                                                errors.reject("Concept.error.multipleShortNames");
×
177
                                        }
178
                                }
179
                                
180
                                //find duplicate names for a non-retired concept
181
                                if (Context.getConceptService().isConceptNameDuplicate(nameInLocale)) {
1✔
182
                                        throw new DuplicateConceptNameException("'" + nameInLocale.getName()
1✔
183
                                                + "' is a duplicate name in locale '" + conceptNameLocale.toString() + "'");
1✔
184
                                }
185
                                
186
                                //
187
                                if (errors.hasErrors()) {
1✔
188
                                        log.debug("Concept name '" + nameInLocale.getName() + "' for locale '" + conceptNameLocale
1✔
189
                                                + "' is invalid");
190
                                        //if validation fails for any conceptName in current locale, don't proceed
191
                                        //This helps not to have multiple messages shown that are identical though they might be
192
                                        //for different conceptNames
193
                                        return;
1✔
194
                                }
195
                                
196
                                //No duplicate names allowed for the same locale and concept, keep the case the same
197
                                //except for short names
198
                                if (!nameInLocale.isShort() && !validNamesFoundInLocale.add(nameInLocale.getName().toLowerCase())) {
1✔
199
                                        throw new DuplicateConceptNameException("'" + nameInLocale.getName()
1✔
200
                                                + "' is a duplicate name in locale '" + conceptNameLocale.toString() + "' for the same concept");
1✔
201
                                }
202
                                
203
                                log.debug("Valid name found: {}", nameInLocale.getName());
1✔
204
                        }
1✔
205
                }
1✔
206
                
207
                //Ensure that each concept has at least a fully specified name
208
                if (!hasFullySpecifiedName && !conceptToValidate.getRetired()) {
1✔
209
                        log.debug("Concept has no fully specified name");
1✔
210
                        errors.reject("Concept.error.no.FullySpecifiedName");
1✔
211
                }
212
                
213
                if (CollectionUtils.isNotEmpty(conceptToValidate.getConceptMappings()) && !conceptToValidate.getRetired()) {
1✔
214
                        //validate all the concept maps
215
                        int index = 0;
1✔
216
                        Set<Integer> mappedTermIds = null;
1✔
217
                        for (ConceptMap map : conceptToValidate.getConceptMappings()) {
1✔
218
                                if (map.getConceptReferenceTerm().getConceptReferenceTermId() == null) {
1✔
219
                                        //if this term is getting created on the fly e.g. from old legacy code, validate it
220
                                        try {
221
                                                errors.pushNestedPath("conceptMappings[" + index + "].conceptReferenceTerm");
1✔
222
                                                ValidationUtils.invokeValidator(new ConceptReferenceTermValidator(), map.getConceptReferenceTerm(),
1✔
223
                                                    errors);
224
                                        }
225
                                        finally {
226
                                                errors.popNestedPath();
1✔
227
                                        }
228
                                        
229
                                }
230

231
                                //don't proceed to the next maps since the current one already has errors
232
                                if (errors.hasErrors()) {
1✔
233
                                        return;
1✔
234
                                }
235
                                
236
                                if (mappedTermIds == null) {
1✔
237
                                        mappedTermIds = new HashSet<>();
1✔
238
                                }
239
                                
240
                                //if we already have a mapping to this term, reject it this map
241
                                if (map.getConceptReferenceTerm().getId() != null
1✔
242
                                        && !mappedTermIds.add(map.getConceptReferenceTerm().getId())) {
1✔
243
                                        errors.rejectValue("conceptMappings[" + index + "]", "ConceptReferenceTerm.term.alreadyMapped",
1✔
244
                                            "Cannot map a reference term multiple times to the same concept");
245
                                }
246
                                
247
                                index++;
1✔
248
                        }
1✔
249
                }
250
                if (CollectionUtils.isNotEmpty(conceptToValidate.getAnswers())) {
1✔
251
                        for (ConceptAnswer conceptAnswer : conceptToValidate.getAnswers()) {
1✔
252
                                if (conceptAnswer.getAnswerConcept().equals(conceptToValidate)) {
1✔
253
                                        errors.reject("Concept.contains.itself.as.answer");
1✔
254
                                }
255
                        }
1✔
256
                }
257
                ValidateUtil.validateFieldLengths(errors, obj.getClass(), "version", "retireReason");
1✔
258
                super.validateAttributes(conceptToValidate, errors, Context.getConceptService().getAllConceptAttributeTypes());
1✔
259
        }
1✔
260
}
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

© 2025 Coveralls, Inc