Skip to content

Commit

Permalink
Update the component to check duplicates in the following fields:
Browse files Browse the repository at this point in the history
- Metadata title
- Metadata alternative title
- Metadata resource identifier
  • Loading branch information
josegar74 committed Nov 4, 2024
1 parent 1dba46e commit 28e66e2
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@

<for name="mrs:referenceSystemIdentifier" addDirective="data-gn-crs-selector"/>

<!-- Example configuration to check duplicated metadata alternate title -->
<!--<for name="cit:alternateTitle"
xpath="/mdb:MD_Metadata/mdb:identificationInfo/mri:MD_DataIdentification/mri:citation/cit:CI_Citation/cit:alternateTitle"
use="data-gn-duplicated-metadata-value-checker">
<directiveAttributes
data-field-key="altTitle" />
</for>-->

<for name="mdb:contact" addDirective="data-gn-directory-entry-selector">
<directiveAttributes
data-template-add-snippet="&lt;cit:CI_Responsibility
Expand Down
69 changes: 32 additions & 37 deletions services/src/main/java/org/fao/geonet/api/records/MetadataApi.java
Original file line number Diff line number Diff line change
@@ -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)
*
Expand All @@ -23,9 +23,6 @@

package org.fao.geonet.api.records;

import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
Expand Down Expand Up @@ -83,7 +80,6 @@

import static org.fao.geonet.kernel.mef.MEFLib.Version.Constants.MEF_V1_ACCEPT_TYPE;
import static org.fao.geonet.kernel.mef.MEFLib.Version.Constants.MEF_V2_ACCEPT_TYPE;
import static org.fao.geonet.kernel.search.EsSearchManager.FIELDLIST_UUID;

@RequestMapping(value = {
"/{portal}/api/records"
Expand Down Expand Up @@ -791,21 +787,21 @@ public FeatureResponse getFeatureCatalog(

@io.swagger.v3.oas.annotations.Operation(summary = "Check if metadata title is duplicated",
description = "Verifies if the metadata title is in use.")
@PostMapping(value = "/{metadataUuid:.+}/checkDuplicatedTitle",
@PostMapping(value = "/{metadataUuid:.+}/checkDuplicatedFieldValue",
produces = {MediaType.APPLICATION_JSON_VALUE})
@PreAuthorize("hasAuthority('Editor')")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Return true if the title is duplicated or false in other case."),
@ApiResponse(responseCode = "403", description = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)
})
public ResponseEntity<Boolean> checkMetadataTitleDuplicated(
public ResponseEntity<Boolean> 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 {
Expand All @@ -815,7 +811,18 @@ public ResponseEntity<Boolean> checkMetadataTitleDuplicated(
throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW);
}

boolean uuidsWithSameTitle = isMetadataTitleExistingInOtherRecords(title, metadataUuid);
List<String> 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);
}

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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)
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -306,7 +307,7 @@ public static Map<RelatedItemType, List<AssociatedRecord>> 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<>());
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* <fields>
* ...
* <for name="gmd:title" use="data-gn-duplicated-metadata-title-checker" />
* <for name="gmd:alternateTitle" use="data-gn-duplicated-metadata-value-checker">
* <directiveAttributes
* data-field-name="ResourceName"
* data-field-key="altTitle" />
*/
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 =
"<p class='help-block' " +
"style='color: #d9534f' " +
"data-ng-show='duplicatedTitle && !hiddenControl' " +
"data-translate>metadataDuplicatedTitle</p>";
"data-ng-show='duplicatedValue && !hiddenControl' " +
"data-translate>" +
duplicatedFieldNameMessage +
"</p>";
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);
Expand All @@ -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);
}
};
}
Expand Down
4 changes: 3 additions & 1 deletion web-ui/src/main/resources/catalog/locales/en-editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}

0 comments on commit 28e66e2

Please sign in to comment.