ValidCropTraitObservationDataValidator.java
/*
* Copyright 2026 Global Crop Diversity Trust
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root folder or http://www.apache.org/licenses/LICENSE-2.0
*/
package org.gringlobal.custom.validation.javax;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.gringlobal.model.CropTraitObservationData;
import org.gringlobal.model.community.CommunityCodeValues;
/**
* Validate that CTOD values match CropTrait definition.
*
* NOTE: Keep this in sync with ValidCropTraitObservationValidator
*/
public class ValidCropTraitObservationDataValidator implements ConstraintValidator<ValidCropTraitObservationData, CropTraitObservationData> {
@Override
public boolean isValid(CropTraitObservationData ctod, ConstraintValidatorContext context) {
try {
if (ctod == null) {
return true;
}
var cropTrait = ctod.getCropTrait();
if (cropTrait == null) {
context.buildConstraintViolationWithTemplate("cropTrait must be non-null")
.addPropertyNode("cropTrait")
.addConstraintViolation();
return false;
}
var numericValue = ctod.getNumericValue();
var stringValue = ctod.getStringValue();
if (numericValue == null && stringValue == null) return true; // No data to validate
var isValid = true;
var dataTypeCode = cropTrait.getDataTypeCode();
// Check NUMERIC type
if (Objects.equals(CommunityCodeValues.CROP_TRAIT_DATA_TYPE_NUMERIC.value, dataTypeCode)) {
if (numericValue != null) {
if (cropTrait.getNumericMinimum() != null && numericValue < cropTrait.getNumericMinimum()) {
context.buildConstraintViolationWithTemplate("numericValue must be more than " + cropTrait.getNumericMinimum())
.addPropertyNode("numericValue")
.addConstraintViolation();
isValid = false;
}
if (cropTrait.getNumericMaximum() != null && numericValue > cropTrait.getNumericMaximum()) {
context.buildConstraintViolationWithTemplate("numericValue must be less than " + cropTrait.getNumericMinimum())
.addPropertyNode("numericValue")
.addConstraintViolation();
isValid = false;
}
}
// Must not have stringValue
if (stringValue != null) {
context.buildConstraintViolationWithTemplate("stringValue must be null for NUMERIC traits")
.addPropertyNode("stringValue")
.addConstraintViolation();
isValid = false;
}
// Check DATE type
} else if (Objects.equals(CommunityCodeValues.CROP_TRAIT_DATA_TYPE_DATE.value, dataTypeCode)) {
if (stringValue != null) {
try {
LocalDate.parse(stringValue);
} catch (DateTimeParseException e) {
context.buildConstraintViolationWithTemplate("stringValue must be a valid date")
.addPropertyNode("stringValue")
.addConstraintViolation();
isValid = false;
}
}
// Must not have numericValue
if (numericValue != null) {
context.buildConstraintViolationWithTemplate("numericValue must be null for DATE traits")
.addPropertyNode("numericValue")
.addConstraintViolation();
isValid = false;
}
// Check CHAR type
} else if (Objects.equals(CommunityCodeValues.CROP_TRAIT_DATA_TYPE_CHAR.value, dataTypeCode)) {
if (stringValue != null) {
if (cropTrait.getMaxLength() != null) {
if (stringValue.length() > cropTrait.getMaxLength()) {
context.buildConstraintViolationWithTemplate("stringValue too long")
.addPropertyNode("stringValue")
.addConstraintViolation();
isValid = false;
}
}
}
// Must not have numericValue
if (numericValue != null) {
context.buildConstraintViolationWithTemplate( "numericValue must be null for CHAR traits")
.addPropertyNode("numericValue")
.addConstraintViolation();
isValid = false;
}
}
return isValid;
} catch (Throwable e) {
return false;
}
}
}