diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java index 6d3fda2812d..96378e87795 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldValidator.java @@ -59,10 +59,26 @@ public boolean isValid(DatasetField value, ConstraintValidatorContext context) { } // if value is not primitive or not empty - if (!dsfType.isPrimitive() || !StringUtils.isBlank(value.getValue())) { + // For controlled vocabulary fields, check that actual CV values are selected, + // not just that datasetFieldValues contains something (which might be an invalid N/A placeholder) + // See https://github.com/IQSS/dataverse/issues/11900 + if (!dsfType.isPrimitive()) { return true; } - + + if (dsfType.isControlledVocabulary()) { + // For CV fields, check if there are actual controlled vocabulary values selected + if (value.getControlledVocabularyValues() != null && !value.getControlledVocabularyValues().isEmpty()) { + return true; + } + // If no CV values, fall through to required field check below + } else { + // For non-CV primitive fields, check if value is not blank + if (!StringUtils.isBlank(value.getValue())) { + return true; + } + } + if (value.isRequired()) { String errorMessage = null; DatasetFieldCompoundValue parent = value.getParentDatasetFieldCompoundValue(); diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 5738ff8cfa9..85d39abd77a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -111,7 +111,6 @@ import org.primefaces.event.FileUploadEvent; import org.primefaces.model.file.UploadedFile; -import jakarta.validation.ConstraintViolation; import java.util.Arrays; import java.util.HashSet; import jakarta.faces.model.SelectItem; @@ -4039,8 +4038,8 @@ public String save() { dataset.setOwner(ownerId != null ? dataverseService.find(ownerId) : null); } // Validate - Set constraintViolations = workingVersion.validate(); - if (!constraintViolations.isEmpty()) { + workingVersion.validate(); // add validation messages to dataset fields + if (!workingVersion.isValid()) { FacesContext.getCurrentInstance().validationFailed(); return ""; } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java index 0b5fae8ee31..bbd8260e3fe 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java @@ -7,20 +7,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import edu.harvard.iq.dataverse.Dataset; -import edu.harvard.iq.dataverse.DatasetField; -import edu.harvard.iq.dataverse.DatasetFieldConstant; -import edu.harvard.iq.dataverse.DatasetFieldServiceBean; -import edu.harvard.iq.dataverse.DatasetFieldType; -import edu.harvard.iq.dataverse.DatasetFieldValue; -import edu.harvard.iq.dataverse.DatasetServiceBean; -import edu.harvard.iq.dataverse.DatasetVersion; -import edu.harvard.iq.dataverse.Dataverse; -import edu.harvard.iq.dataverse.DataverseContact; -import edu.harvard.iq.dataverse.DataverseServiceBean; -import edu.harvard.iq.dataverse.EjbDataverseEngine; -import edu.harvard.iq.dataverse.GlobalId; -import edu.harvard.iq.dataverse.MetadataBlockServiceBean; +import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.dto.DatasetDTO; import edu.harvard.iq.dataverse.api.imports.ImportUtil.ImportType; import edu.harvard.iq.dataverse.dataset.DatasetTypeServiceBean; @@ -768,13 +755,22 @@ private boolean validateVersionMetadata(DatasetVersion version, boolean sanitize Object invalid = v.getRootBean(); String msg = ""; if (invalid instanceof DatasetField) { - DatasetField f = (DatasetField) invalid; - - msg += "Missing required field: " + f.getDatasetFieldType().getDisplayName() + ";"; + DatasetField f = (DatasetField) invalid; + + msg += "Missing required field: " + f.getDatasetFieldType().getDisplayName() + ";"; if (sanitize) { - msg += " populated with '" + DatasetField.NA_VALUE + "'"; - f.setSingleValue(DatasetField.NA_VALUE); - fixed = true; + if (f.getDatasetFieldType().isControlledVocabulary()) { + ControlledVocabularyValue naValue = datasetfieldService.findNAControlledVocabularyValue(); + if (naValue != null) { + f.setControlledVocabularyValues(List.of(naValue)); + msg += " populated with '" + DatasetField.NA_VALUE + "'"; + fixed = true; + } + } else { + f.setSingleValue(DatasetField.NA_VALUE); + msg += " populated with '" + DatasetField.NA_VALUE + "'"; + fixed = true; + } } } else if (invalid instanceof DatasetFieldValue) { DatasetFieldValue fv = (DatasetFieldValue) invalid; diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java index 2d09b22925f..096238f5637 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java @@ -110,10 +110,14 @@ protected void validateOrDie(DatasetVersion dsv, Boolean lenient) throws Command Set constraintViolations = dsv.validate(); if (!constraintViolations.isEmpty()) { if (lenient) { - // populate invalid fields with N/A + // populate invalid primitive fields with N/A + // Note: controlled vocabulary fields should NOT get N/A values in datasetfieldvalue, + // as this creates an inconsistent state where the CV field appears valid but is empty. + // See https://github.com/IQSS/dataverse/issues/11900 constraintViolations.stream() .filter(cv -> cv.getRootBean() instanceof DatasetField) .map(cv -> ((DatasetField) cv.getRootBean())) + .filter(f -> !f.getDatasetFieldType().isControlledVocabulary()) .forEach(f -> f.setSingleValue(DatasetField.NA_VALUE)); } else {