/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.validator;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.ConceptName;
import org.openmrs.annotation.Handler;
import org.openmrs.api.APIException;
import org.openmrs.api.DuplicateConceptNameException;
import org.openmrs.api.context.Context;
import org.openmrs.util.LocaleUtility;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* Validates {@link Concept} objects.
*/
@Handler(supports = { Concept.class }, order = 50)
public class ConceptValidator implements Validator {
// Log for this class
private static final Log log = LogFactory.getLog(ConceptValidator.class);
/**
* Determines if the command object being submitted is a valid type
*
* @see org.springframework.validation.Validator#supports(java.lang.Class)
*/
@SuppressWarnings("unchecked")
public boolean supports(Class c) {
return c.equals(Concept.class);
}
/**
* Checks that a given concept object is valid.
*
* @see org.springframework.validation.Validator#validate(java.lang.Object,
* org.springframework.validation.Errors)
* @should pass if the concept has atleast one fully specified name added to it
* @should fail if there is a duplicate unretired concept name in the locale
* @should fail if there is a duplicate unretired preferred name in the same locale
* @should fail if there is a duplicate unretired fully specified name in the same locale
* @should fail if any names in the same locale for this concept are similar
* @should pass if the concept with a duplicate name is retired
* @should fail if any name is an empty string
* @should fail if the object parameter is null
* @should pass if the concept is being updated with no name change
* @should fail if any name is a null value
* @should not allow multiple preferred names in a given locale
* @should not allow multiple fully specified conceptNames in a given locale
* @should not allow multiple short names in a given locale
* @should not allow an index term to be a locale preferred name
* @should fail if there is no name explicitly marked as fully specified
* @should pass if the duplicate ConceptName is neither preferred nor fully Specified
*/
public void validate(Object obj, Errors errors) throws APIException, DuplicateConceptNameException {
if (obj == null || !(obj instanceof Concept))
throw new IllegalArgumentException("The parameter obj should not be null and must be of type" + Concept.class);
Concept conceptToValidate = (Concept) obj;
//no name to validate, but why is this the case?
if (conceptToValidate.getNames().size() == 0) {
errors.reject("Concept.name.atLeastOneRequired");
return;
}
boolean hasFullySpecifiedName = false;
for (Locale conceptNameLocale : conceptToValidate.getAllConceptNameLocales()) {
//The concept's locale should be among the allowed locales listed in global properties
if (!LocaleUtility.getLocalesInOrder().contains(conceptNameLocale)) {
log.warn("The locale '" + conceptNameLocale.toString() + "' is not listed among allowed locales");
errors.reject("Concept.error.invalid.locale");
}
boolean fullySpecifiedNameForLocaleFound = false;
boolean preferredNameForLocaleFound = false;
boolean shortNameForLocaleFound = false;
Set<String> validNamesFoundInLocale = new HashSet<String>();
Collection<ConceptName> namesInLocale = conceptToValidate.getNames(conceptNameLocale);
for (ConceptName nameInLocale : namesInLocale) {
if (StringUtils.isBlank(nameInLocale.getName())) {
log.debug("Name in locale '" + conceptNameLocale.toString()
+ "' cannot be an empty string or white space");
errors.reject("Concept.name.empty");
}
if (nameInLocale.isLocalePreferred() != null) {
if (nameInLocale.isLocalePreferred() && !preferredNameForLocaleFound) {
if (nameInLocale.isIndexTerm()) {
log.warn("Preferred name in locale '" + conceptNameLocale.toString()
+ "' shouldn't be an index term");
errors.reject("Concept.error.preferredName.is.indexTerm");
} else if (nameInLocale.isShort()) {
log.warn("Preferred name in locale '" + conceptNameLocale.toString()
+ "' shouldn't be a short name");
errors.reject("Concept.error.preferredName.is.shortName");
} else if (nameInLocale.isVoided()) {
log.warn("Preferred name in locale '" + conceptNameLocale.toString()
+ "' shouldn't be a voided name");
errors.reject("Concept.error.preferredName.is.voided");
}
preferredNameForLocaleFound = true;
}
//should have one preferred name per locale
else if (nameInLocale.isLocalePreferred() && preferredNameForLocaleFound) {
log.warn("Found multiple preferred names in locale '" + conceptNameLocale.toString() + "'");
errors.reject("Concept.error.multipleLocalePreferredNames");
}
}
if (nameInLocale.isFullySpecifiedName()) {
if (!hasFullySpecifiedName)
hasFullySpecifiedName = true;
if (!fullySpecifiedNameForLocaleFound)
fullySpecifiedNameForLocaleFound = true;
else {
log.warn("Found multiple fully specified names in locale '" + conceptNameLocale.toString() + "'");
errors.reject("Concept.error.multipleFullySpecifiedNames");
}
if (nameInLocale.isVoided()) {
log.warn("Fully Specified name in locale '" + conceptNameLocale.toString()
+ "' shouldn't be a voided name");
errors.reject("Concept.error.fullySpecifiedName.is.voided");
}
}
if (nameInLocale.isShort()) {
if (!shortNameForLocaleFound)
shortNameForLocaleFound = true;
//should have one short name per locale
else {
log.warn("Found multiple short names in locale '" + conceptNameLocale.toString() + "'");
errors.reject("Concept.error.multipleShortNames");
}
}
if (nameInLocale.isLocalePreferred() || nameInLocale.isFullySpecifiedName()) {
List<Concept> conceptsWithPossibleDuplicateNames = Context.getConceptService().getConceptsByName(
nameInLocale.getName());
if (conceptsWithPossibleDuplicateNames.size() > 0) {
for (Concept concept : conceptsWithPossibleDuplicateNames) {
//skip past the concept being edited and retired ones
if (concept.isRetired()
|| (conceptToValidate.getConceptId() != null && conceptToValidate.getConceptId().equals(
concept.getConceptId())))
continue;
//should be a unique name amongst all preferred and fully specified names in its locale system wide
if ((concept.getFullySpecifiedName(conceptNameLocale) != null && concept.getFullySpecifiedName(
conceptNameLocale).getName().equalsIgnoreCase(nameInLocale.getName()))
|| (concept.getPreferredName(conceptNameLocale) != null && concept.getPreferredName(
conceptNameLocale).getName().equalsIgnoreCase(nameInLocale.getName()))) {
throw new DuplicateConceptNameException("'" + nameInLocale.getName()
+ "' is a duplicate name in locale '" + conceptNameLocale.toString() + "'");
}
}
}
}
if (errors.hasErrors()) {
log.debug("Concept name '" + nameInLocale.getName() + "' for locale '" + conceptNameLocale
+ "' is invalid");
//if validation fails for any conceptName in current locale, don't proceed
//This helps not to have multiple messages shown that are identical though they might be
//for different conceptNames
return;
}
//No duplicate names allowed for the same locale and concept, keep the case the same
if (!validNamesFoundInLocale.add(nameInLocale.getName().toLowerCase()))
throw new DuplicateConceptNameException("'" + nameInLocale.getName()
+ "' is a duplicate name in locale '" + conceptNameLocale.toString() + "' for the same concept");
if (log.isDebugEnabled())
log.debug("Valid name found: " + nameInLocale.getName());
}
}
//Ensure that each concept has atleast a fully specified name
if (!hasFullySpecifiedName) {
log.debug("Concept has no fully specified name");
errors.reject("Concept.error.no.FullySpecifiedName");
}
}
}