/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.validator;
import org.apache.commons.lang.StringUtils;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.PatientIdentifierType.LocationBehavior;
import org.openmrs.PatientIdentifierType.UniquenessBehavior;
import org.openmrs.annotation.Handler;
import org.openmrs.api.BlankIdentifierException;
import org.openmrs.api.IdentifierNotUniqueException;
import org.openmrs.api.InvalidCheckDigitException;
import org.openmrs.api.InvalidIdentifierFormatException;
import org.openmrs.api.PatientIdentifierException;
import org.openmrs.api.context.Context;
import org.openmrs.patient.IdentifierValidator;
import org.openmrs.patient.UnallowedIdentifierException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* This class validates a PatientIdentifier object.
*/
@Handler(supports = { PatientIdentifier.class }, order = 50)
public class PatientIdentifierValidator implements Validator {
private static Logger log = LoggerFactory.getLogger(PatientIdentifierValidator.class);
/**
* @see org.springframework.validation.Validator#supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> c) {
return PatientIdentifier.class.isAssignableFrom(c);
}
/**
* Validates a PatientIdentifier.
*
* @see org.springframework.validation.Validator#validate(java.lang.Object,
* org.springframework.validation.Errors)
* @should pass validation if field lengths are correct
* @should fail validation if field lengths are not correct
*/
@Override
public void validate(Object obj, Errors errors) {
PatientIdentifier pi = (PatientIdentifier) obj;
try {
validateIdentifier(pi);
ValidateUtil.validateFieldLengths(errors, obj.getClass(), "identifier", "voidReason");
}
catch (Exception e) {
errors.reject(e.getMessage());
}
}
/**
* Checks that the given {@link PatientIdentifier} is valid
*
* @param pi - the {@link PatientIdentifier} to validate
* @throws PatientIdentifierException if the {@link PatientIdentifier} is invalid
* @should fail validation if PatientIdentifier is null
* @should pass validation if PatientIdentifier is voided
* @should fail validation if another patient has a matching identifier of the same type
* @should pass if in use and id type uniqueness is set to non unique
* @see #validateIdentifier(String, PatientIdentifierType)
*/
public static void validateIdentifier(PatientIdentifier pi) throws PatientIdentifierException {
// Validate that the identifier is non-null
if (pi == null) {
throw new BlankIdentifierException("PatientIdentifier.error.null");
}
// Only validate if the PatientIdentifier is not voided
if (!pi.getVoided()) {
// Check that this is a valid identifier
validateIdentifier(pi.getIdentifier(), pi.getIdentifierType());
// Check that location is included if it is required (default behavior is to require it)
LocationBehavior lb = pi.getIdentifierType().getLocationBehavior();
if (pi.getLocation() == null && (lb == null || lb == LocationBehavior.REQUIRED)) {
String identifierString = (pi.getIdentifier() != null) ? pi.getIdentifier() : "";
throw new PatientIdentifierException(Context.getMessageSourceService().getMessage(
"PatientIdentifier.location.null", new Object[] { identifierString }, Context.getLocale()));
}
if (pi.getIdentifierType().getUniquenessBehavior() != UniquenessBehavior.NON_UNIQUE
&& Context.getPatientService().isIdentifierInUseByAnotherPatient(pi)) {
// Check is already in use by another patient
throw new IdentifierNotUniqueException(Context.getMessageSourceService().getMessage(
"PatientIdentifier.error.notUniqueWithParameter", new Object[] { pi.getIdentifier() },
Context.getLocale()), pi);
}
}
}
/**
* Validates that a given identifier string is valid for a given {@link PatientIdentifierType}
* Checks for things like blank identifiers, invalid check digits, and invalid format.
*
* @param pit - the {@link PatientIdentifierType} to validate against
* @param identifier - the identifier to check against the passed {@link PatientIdentifierType}
* @throws PatientIdentifierException if the identifier is invalid
* @should fail validation if PatientIdentifierType is null
* @should fail validation if identifier is blank
* @see #checkIdentifierAgainstFormat(String, String, String)
* @see #checkIdentifierAgainstValidator(String, IdentifierValidator)
*/
public static void validateIdentifier(String identifier, PatientIdentifierType pit) throws PatientIdentifierException {
log.debug("Checking identifier: " + identifier + " for type: " + pit);
// Validate input parameters
if (pit == null) {
throw new BlankIdentifierException("PatientIdentifierType.null");
}
if (StringUtils.isBlank(identifier)) {
throw new BlankIdentifierException("PatientIdentifier.error.nullOrBlank");
}
checkIdentifierAgainstFormat(identifier, pit.getFormat(), pit.getFormatDescription());
// Check identifier against IdentifierValidator
if (pit.hasValidator()) {
IdentifierValidator validator = Context.getPatientService().getIdentifierValidator(pit.getValidator());
checkIdentifierAgainstValidator(identifier, validator);
}
log.debug("The identifier check was successful");
}
/**
* Validates that a given identifier string is valid for a given regular expression format
*
* @param identifier - the identifier to check against the passed {@link PatientIdentifierType}
* @param format - the regular expression format to validate against
* @param formatDescription - user-friendly way of describing format (may be null)
* @throws PatientIdentifierException if the identifier is does not match the format
* @should fail validation if identifier is blank
* @should fail validation if identifier does not match the format
* @should pass validation if identifier matches the format
* @should pass validation if the format is blank
* @should include format in error message if no formatDescription is specified
* @should include formatDescription in error message if specified
*/
public static void checkIdentifierAgainstFormat(String identifier, String format, String formatDescription)
throws PatientIdentifierException {
log.debug("Checking identifier: " + identifier + " against format: " + format);
if (StringUtils.isBlank(identifier)) {
throw new BlankIdentifierException("PatientIdentifier.error.nullOrBlank");
}
if (StringUtils.isBlank(format)) {
log.debug("Format is blank, identifier passes.");
return;
}
// Check identifier against regular expression format
if (!identifier.matches(format)) {
log.debug("The two DO NOT match");
throw new InvalidIdentifierFormatException(getMessage("PatientIdentifier.error.invalidFormat", identifier,
StringUtils.isNotBlank(formatDescription) ? formatDescription : format));
}
log.debug("The two match!!");
}
/**
* Validates that a given identifier string is valid for a given IdentifierValidator
*
* @param identifier the identifier to check against the passed {@link PatientIdentifierType}
* @param validator the IdentifierValidator to use to check the identifier
* @throws PatientIdentifierException if the identifier is does not match the format
* @should fail validation if identifier is blank
* @should fail validation if identifier is invalid
* @should pass validation if identifier is valid
* @should pass validation if validator is null
*/
public static void checkIdentifierAgainstValidator(String identifier, IdentifierValidator validator)
throws PatientIdentifierException {
log.debug("Checking identifier: " + identifier + " against validator: " + validator);
if (StringUtils.isBlank(identifier)) {
throw new BlankIdentifierException("PatientIdentifier.error.nullOrBlank");
}
if (validator == null) {
log.debug("Validator is null, identifier passes.");
return;
}
// Check identifier against IdentifierValidator
try {
if (!validator.isValid(identifier)) {
throw new InvalidCheckDigitException(getMessage("PatientIdentifier.error.checkDigitWithParameter",
identifier));
}
}
catch (UnallowedIdentifierException e) {
throw new InvalidCheckDigitException(getMessage("PatientIdentifier.error.unallowedIdentifier", identifier,
validator.getName()));
}
log.debug("The identifier passed validation.");
}
private static String getMessage(String messageKey, String... arguments) {
return Context.getMessageSourceService().getMessage(messageKey, arguments, Context.getLocale());
}
}