diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/config-editor.xml b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/config-editor.xml index e017378389d..2a0518e14b9 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/config-editor.xml +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/config-editor.xml @@ -98,6 +98,14 @@ + + + checkMetadataTitleDuplicated( + public ResponseEntity checkDuplicatedFieldValue( @Parameter(description = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid, - @Parameter(description = "Metadata title to check", + @Parameter(description = "Metadata field information to check", required = true) - @RequestBody String title, + @RequestBody DuplicatedValueDto duplicatedValueDto, HttpServletRequest request ) throws Exception { try { @@ -815,7 +811,18 @@ public ResponseEntity checkMetadataTitleDuplicated( throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); } - boolean uuidsWithSameTitle = isMetadataTitleExistingInOtherRecords(title, metadataUuid); + List validFields = Arrays.asList("title", "altTitle", "identifier"); + + if (!validFields.contains(duplicatedValueDto.getField())) { + throw new IllegalArgumentException(String.format("A valid Lucene field name is required:", String.join(",", validFields))); + } + + if (StringUtils.isEmpty(duplicatedValueDto.getValue())) { + throw new IllegalArgumentException("A non-empty value is required."); + } + + + boolean uuidsWithSameTitle = MetadataUtils.isMetadataFieldValueExistingInOtherRecords(duplicatedValueDto.getValue(), duplicatedValueDto.getField(), metadataUuid); return ResponseEntity.ok(uuidsWithSameTitle); } @@ -828,36 +835,24 @@ private boolean isIncludedAttributeTable(RelatedResponse.Fcat fcat) { && fcat.getItem().get(0).getFeatureType().getAttributeTable().getElement() != null; } + private static class DuplicatedValueDto { + private String field; + private String value; - /** - * Check if other metadata records exist apart from the one with {code}metadataUuidToExclude{code} with - * {code}metadataTitle{code} title in the catalogue. - * - * @param metadataTitle Metadata title to check. - * @param metadataUuidToExclude Metadata identifier to exclude from the search. - * @return A list of metadata uuids that have the same metadata title. - */ - private boolean isMetadataTitleExistingInOtherRecords(String metadataTitle, String metadataUuidToExclude) { - boolean metadataWithSameTitle = false; - String jsonQuery = " {" + - " \"query_string\": {" + - " \"query\": \"+resourceTitleObject.\\\\*.keyword:\\\"%s\\\" -uuid:\\\"%s\\\"\"" + - " }" + - "}"; - - ObjectMapper objectMapper = new ObjectMapper(); - try { - JsonNode esJsonQuery = objectMapper.readTree(String.format(jsonQuery, metadataTitle, metadataUuidToExclude)); + public String getField() { + return field; + } - final SearchResponse queryResult = esSearchManager.query( - esJsonQuery, - FIELDLIST_UUID, - 0, 5); + public void setField(String field) { + this.field = field; + } + + public String getValue() { + return value; + } - metadataWithSameTitle = !queryResult.hits().hits().isEmpty(); - } catch (Exception ex) { - Log.error(API.LOG_MODULE_NAME, ex.getMessage(), ex); + public void setValue(String value) { + this.value = value; } - return metadataWithSameTitle; } } diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataUtils.java b/services/src/main/java/org/fao/geonet/api/records/MetadataUtils.java index 76bb7f594f3..09a9794a489 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataUtils.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2023 Food and Agriculture Organization of the + * Copyright (C) 2001-2024 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -37,6 +37,7 @@ import org.fao.geonet.ApplicationContextHolder; import org.fao.geonet.GeonetContext; import org.fao.geonet.NodeInfo; +import org.fao.geonet.api.API; import org.fao.geonet.api.es.EsHTTPProxy; import org.fao.geonet.api.records.model.related.AssociatedRecord; import org.fao.geonet.api.records.model.related.RelatedItemOrigin; @@ -306,7 +307,7 @@ public static Map> getAssociated( if (!e.fields().isEmpty()) { FIELDLIST_RELATED_SCRIPTED.keySet().forEach(f -> { JsonData dc = (JsonData) e.fields().get(f); - + if (dc != null) { if (associatedRecord.getProperties() == null) { associatedRecord.setProperties(new HashMap<>()); @@ -774,6 +775,48 @@ public static boolean retrieveMetadataValidationStatus(AbstractMetadata metadata return isInvalid; } + /** + * Check if other metadata records exist apart from the one with {code}metadataUuidToExclude{code} with the same + * {code}metadataValue{code} for the field {code}metadataField{code}. + * + * @param metadataValue Metadata value to check. + * @param metadataField Metadata field to check the value. + * @param metadataUuidToExclude Metadata identifier to exclude from the search. + * @return A list of metadata uuids that have the same value for the field provided. + */ + public static boolean isMetadataFieldValueExistingInOtherRecords(String metadataValue, String metadataField, String metadataUuidToExclude) { + ApplicationContext applicationContext = ApplicationContextHolder.get(); + EsSearchManager searchMan = applicationContext.getBean(EsSearchManager.class); + + String esFieldName = "resourceTitleObject.\\\\*.keyword"; + if (metadataField.equals("altTitle")) { + esFieldName = "resourceAltTitleObject.\\\\*.keyword"; + } else if (metadataField.equals("identifier")) { + esFieldName = "resourceIdentifier.code"; + } + + boolean duplicatedMetadataValue = false; + String jsonQuery = " {" + + " \"query_string\": {" + + " \"query\": \"+" + esFieldName + ":\\\"%s\\\" -uuid:\\\"%s\\\"\"" + + " }" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + try { + JsonNode esJsonQuery = objectMapper.readTree(String.format(jsonQuery, metadataValue, metadataUuidToExclude)); + + final SearchResponse queryResult = searchMan.query( + esJsonQuery, + FIELDLIST_UUID, + 0, 5); + + duplicatedMetadataValue = !queryResult.hits().hits().isEmpty(); + } catch (Exception ex) { + Log.error(API.LOG_MODULE_NAME, ex.getMessage(), ex); + } + return duplicatedMetadataValue; + } /** * Checks if a result for a search query has results. diff --git a/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js b/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js index f8b146c386d..c8d6e5e6c26 100644 --- a/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js +++ b/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js @@ -545,43 +545,59 @@ /** * @ngdoc directive - * @name gn_fields.directive:gnDuplicatedMetadataTitleChecker + * @name gn_fields.directive:gnDuplicatedMetadataValueChecker * * @description - * Checks if the associated control value exists in another metadata record title. + * Checks if the associated control value exists in another metadata record. + * Valid field keys: + * - title: Metadata title. + * - altTitle: Metadata alternative title. + * - identifier: Metadata resource identifier. * Configure in your metadata schema config-editor.xml the usage of this directive * for the title element. For example, for iso19139: * * ... - * + * + * */ - module.directive("gnDuplicatedMetadataTitleChecker", [ + module.directive("gnDuplicatedMetadataValueChecker", [ "gnCurrentEdit", "$http", "$compile", - function (gnCurrentEdit, $http, $compile) { + "$translate", + function (gnCurrentEdit, $http, $compile, $translate) { return { restrict: "A", - scope: {}, + scope: { + fieldKey: "@" // Elasticsearch field name. Allowed values: title (Metadata title), altTitle (Metadata alternate title), identifier (Resource identifier) + }, link: function (scope, element, attrs) { + var duplicatedFieldNameMessage = $translate.instant( + "metadataDuplicatedField-" + scope.fieldKey + ); + var messageTemplate = "

metadataDuplicatedTitle

"; + "data-ng-show='duplicatedValue && !hiddenControl' " + + "data-translate>" + + duplicatedFieldNameMessage + + "

"; var messageTemplateCompiled = $compile(messageTemplate)(scope); - var messageTarget = element.context; + var messageTarget = document.getElementById(element[0].id); element.blur(function () { - if (messageTarget.value !== scope.metadataTitle) { - scope.metadataTitle = messageTarget.value; - scope.checkTitle(scope.metadataTitle, scope.metadataUuid); + if (messageTarget.value !== scope.metadataFieldValue) { + scope.metadataFieldValue = messageTarget.value; + scope.checkField(scope.metadataFieldValue, scope.metadataUuid); } }); scope.metadataUuid = gnCurrentEdit.uuid; - scope.metadataTitle = messageTarget.value; - scope.duplicatedTitle = false; + scope.metadataFieldValue = messageTarget.value; + scope.duplicatedValue = false; scope.hiddenControl = false; element.after(messageTemplateCompiled); @@ -602,18 +618,28 @@ characterData: false }); - scope.checkTitle = function (metadataTitle, metadataUuid) { + scope.checkField = function (fieldValue, metadataUuid) { + if (fieldValue === "") { + scope.duplicatedValue = false; + return; + } + + var postBody = { + field: scope.fieldKey, + value: fieldValue + }; + $http .post( - "../api/records/" + metadataUuid + "/checkDuplicatedTitle", - metadataTitle + "../api/records/" + metadataUuid + "/checkDuplicatedFieldValue", + postBody ) .then(function (response) { - scope.duplicatedTitle = response.data === true; + scope.duplicatedValue = response.data === true; }); }; - scope.checkTitle(scope.metadataTitle, scope.metadataUuid); + scope.checkField(scope.metadataFieldValue, scope.metadataUuid); } }; } diff --git a/web-ui/src/main/resources/catalog/locales/en-editor.json b/web-ui/src/main/resources/catalog/locales/en-editor.json index c22d92c6e5a..8245a616e85 100644 --- a/web-ui/src/main/resources/catalog/locales/en-editor.json +++ b/web-ui/src/main/resources/catalog/locales/en-editor.json @@ -460,5 +460,7 @@ "associatedResourcesPanel": "Associated resources", "validationSuccessLabel": "success", "validationErrorLabel": "errors", - "metadataDuplicatedTitle": "The title is used in another metadata record." + "metadataDuplicatedField-title": "The metadata title is used in another metadata record.", + "metadataDuplicatedField-altTitle": "The metadata alternate title is used in another metadata record.", + "metadataDuplicatedField-identifier": "The metadata resource identifier is used in another metadata record." }