package eu.eudat.service.prefilling; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.enums.FieldType; import eu.eudat.commons.types.descriptiontemplate.FieldEntity; import eu.eudat.commons.types.descriptiontemplate.fielddata.AutoCompleteDataEntity; import eu.eudat.commons.types.descriptiontemplate.fielddata.ComboBoxDataEntity; import eu.eudat.commons.types.descriptiontemplate.fielddata.LabelAndMultiplicityDataEntity; import eu.eudat.commons.types.descriptiontemplate.fielddata.WordListDataEntity; import eu.eudat.commons.types.prefilling.PrefillingEntity; import eu.eudat.convention.ConventionService; import eu.eudat.data.DescriptionTemplateEntity; import eu.eudat.data.ReferenceEntity; import eu.eudat.model.*; import eu.eudat.model.builder.DescriptionTemplateBuilder; import eu.eudat.model.builder.PrefillingBuilder; import eu.eudat.model.builder.ReferenceBuilder; import eu.eudat.model.descriptionproperties.Field; import eu.eudat.model.descriptionproperties.PropertyDefinition; import eu.eudat.model.persist.DescriptionProfilingRequest; import eu.eudat.model.persist.DescriptionProfilingWithDataRequest; import eu.eudat.query.ReferenceQuery; import eu.eudat.service.remotefetcher.ExternalUrlConfigProvider; import eu.eudat.service.remotefetcher.RemoteFetcherService; import eu.eudat.service.remotefetcher.config.AuthenticationConfiguration; import eu.eudat.service.remotefetcher.config.DataFieldsUrlConfiguration; import eu.eudat.service.remotefetcher.config.DataUrlConfiguration; import eu.eudat.service.remotefetcher.config.UrlConfiguration; import eu.eudat.service.remotefetcher.config.entities.*; import eu.eudat.service.remotefetcher.criteria.ExternalReferenceCriteria; import eu.eudat.service.remotefetcher.criteria.FetchStrategy; import eu.eudat.service.remotefetcher.models.ExternalAutocompleteFieldResult; import eu.eudat.utilities.helpers.StreamDistinctBy; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.validation.ValidatorFactory; import jakarta.persistence.EntityManager; import jakarta.xml.bind.JAXBException; import org.json.JSONObject; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @Service public class PrefillingServiceImpl implements PrefillingService { private final static String Zenodo = "zenodo"; private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrefillingServiceImpl.class)); private final EntityManager entityManager; private final BuilderFactory builderFactory; private final QueryFactory queryFactory; private final ConventionService conventionService; private final MessageSource messageSource; private final RemoteFetcherService remoteFetcherService; private final JsonHandlingService jsonHandlingService; private final ExternalUrlConfigProvider externalUrlConfigProvider; private final XmlHandlingService xmlHandlingService; private final ValidatorFactory validatorFactory; @Autowired public PrefillingServiceImpl( EntityManager entityManager, BuilderFactory builderFactory, QueryFactory queryFactory, ConventionService conventionService, MessageSource messageSource, RemoteFetcherService remoteFetcherService, JsonHandlingService jsonHandlingService, ExternalUrlConfigProvider externalUrlConfigProvider, XmlHandlingService xmlHandlingService, ValidatorFactory validatorFactory) { this.entityManager = entityManager; this.builderFactory = builderFactory; this.queryFactory = queryFactory; this.conventionService = conventionService; this.messageSource = messageSource; this.remoteFetcherService = remoteFetcherService; this.jsonHandlingService = jsonHandlingService; this.externalUrlConfigProvider = externalUrlConfigProvider; this.xmlHandlingService = xmlHandlingService; this.validatorFactory = validatorFactory; } @Override public List getPrefillings(PrefillingLookup lookup) { logger.debug(new MapLogEntry("persisting data").And("lookup", lookup)); ExternalReferenceCriteria externalReferenceCriteria = new ExternalReferenceCriteria(); externalReferenceCriteria.setLike(lookup.getLike()); List prefillings = new ArrayList<>(); List> map; Map prefillingConfigs = this.externalUrlConfigProvider.getExternalUrls().getPrefillings(); for (PrefillingConfig prefillingConfig: prefillingConfigs.values()) { map = remoteFetcherService.getExternalGeneric(externalReferenceCriteria, prefillingConfig.getPrefillingSearch()); prefillings.addAll(map.stream().map(submap -> PrefillingEntity.build(submap, this.jsonHandlingService)).toList()); if (prefillingConfig.getPrefillingSearch().getUrlConfig().isDataInListing()) { List> mapData = remoteFetcherService.getExternalGenericWithData(externalReferenceCriteria, prefillingConfig.getPrefillingSearch()); for (int i = 0; i < mapData.size(); i++) { prefillings.get(i).setData(mapData.get(i)); } prefillings = prefillings.stream().filter(prefilling -> prefilling.getData() != null).collect(Collectors.toList()); } } return this.builderFactory.builder(PrefillingBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(lookup.getProject(), Prefilling._pid), prefillings); } @Override public Description getPrefilledDescription(DescriptionProfilingRequest model) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException { PrefillingConfig prefillingConfig = this.externalUrlConfigProvider.getExternalUrls().getPrefillings().get(model.getConfigId()); PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet(); Map prefillingEntity = getSinglePrefillingData(prefillingGet.getUrl(), model.getPrefillId()); DescriptionProfilingWithDataRequest descriptionProfilingWithDataRequest = new DescriptionProfilingWithDataRequest(); descriptionProfilingWithDataRequest.setConfigId(model.getConfigId()); descriptionProfilingWithDataRequest.setProject(model.getProject()); descriptionProfilingWithDataRequest.setDescriptionTemplateId(model.getDescriptionTemplateId()); descriptionProfilingWithDataRequest.setData(prefillingEntity); validatorFactory.validator(DescriptionProfilingWithDataRequest.DescriptionProfilingWithDataRequestValidator.ValidatorName).validateForce(descriptionProfilingWithDataRequest); return this.getPrefilledDescriptionUsingData(descriptionProfilingWithDataRequest); } private Map getSinglePrefillingData(String url, String id) { RestTemplate restTemplate = new RestTemplate(); String parsedUrl = url.replace("{id}", id); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); HttpEntity entity = new HttpEntity("", headers); return restTemplate.exchange(parsedUrl, HttpMethod.GET, entity, LinkedHashMap.class).getBody(); } @Override public Description getPrefilledDescriptionUsingData(DescriptionProfilingWithDataRequest model) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException { PrefillingConfig prefillingConfig = this.externalUrlConfigProvider.getExternalUrls().getPrefillings().get(model.getConfigId()); PrefillingGet prefillingGet = prefillingConfig.getPrefillingGet(); DescriptionTemplateEntity descriptionTemplateEntity = this.entityManager.find(DescriptionTemplateEntity.class, model.getDescriptionTemplateId()); if (descriptionTemplateEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDescriptionTemplateId(), DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); eu.eudat.commons.types.descriptiontemplate.DefinitionEntity descriptionTemplateDefinition = this.xmlHandlingService.fromXml(eu.eudat.commons.types.descriptiontemplate.DefinitionEntity.class, descriptionTemplateEntity.getDefinition()); Description description = new Description(); description.setDescriptionTemplate(this.builderFactory.builder(DescriptionTemplateBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(model.getProject(), descriptionTemplateEntity)); return mapPrefilledEntityToDescription(description, model.getData(), prefillingGet, prefillingConfig.getType(), descriptionTemplateDefinition); } //region Description Mapping private Object getValueFromPath(Map data, String path){ if (path == null || this.conventionService.isNullOrEmpty(path) || data == null) return null; if (path.contains(".")){ String[] paths = path.split("\\."); if (paths.length == 0) return null; else { Object value = data.getOrDefault(paths[0], null); if (value instanceof Map) return this.getValueFromPath((Map) value, path.substring(paths[0].length() + 1)); else return null; } }else { return data.getOrDefault(path, null); } } private Description mapPrefilledEntityToDescription(Description description, Map prefilledData, PrefillingGet prefillingGet, String type, eu.eudat.commons.types.descriptiontemplate.DefinitionEntity descriptionTemplateDefinition){ for (DefaultPrefillingMapping prefillingMapping: prefillingGet.getMappings()) { Object sourceValue = this.getValueFromPath(prefilledData, prefillingMapping.getSource()); try { setValueToDescription(description, prefillingMapping, sourceValue, descriptionTemplateDefinition, type); } catch (Exception e) { logger.warn("Couldn't map " + (this.conventionService.isNullOrEmpty(prefillingMapping.getSemanticTarget()) ? prefillingMapping.getTarget() : prefillingMapping.getSemanticTarget())); } } for (PrefillingFixedMapping fixedMapping: prefillingGet.getFixedMappings()) { try { setValueToDescription(description, fixedMapping, fixedMapping.getValue(), descriptionTemplateDefinition, type); } catch (Exception e) { logger.warn("Couldn't map " + (this.conventionService.isNullOrEmpty(fixedMapping.getSemanticTarget()) ? fixedMapping.getTarget() : fixedMapping.getSemanticTarget())); } } return description; } private void setValueToDescription(Description description, PrefillingMapping prefillingMapping, Object value, eu.eudat.commons.types.descriptiontemplate.DefinitionEntity definition, String type) { JsonNode valueNode = new ObjectMapper().valueToTree(value); String parsedValue = this.getValueAsString(prefillingMapping, valueNode); List parsedValues = this.getValueAsStringArray(prefillingMapping, valueNode); if (prefillingMapping.getTarget() != null) { this.applyValueToDescriptionObject(description, prefillingMapping, parsedValue, parsedValues); } else { // zenodo prefilling customizations if(type.equals(PrefillingServiceImpl.Zenodo)){ if(prefillingMapping.getSemanticTarget().equals("rda.dataset.distribution.data_access")){ if(parsedValue != null && parsedValue.equals("open")){ List issuedFieldEntities = definition.getAllField().stream().filter(x-> x.getSchematics() != null && x.getSchematics().contains("rda.dataset.issued")).toList(); if(this.conventionService.isListNullOrEmpty(issuedFieldEntities)){ String issuedIdNode = issuedFieldEntities.getFirst().getId(); String issuedValue = description.getProperties().getFields().stream().filter(x-> x.getKey().equals(issuedIdNode)).map(Field::getValue).findFirst().orElse(null);//TODO List licStartEntities = definition.getAllField().stream().filter(x-> x.getSchematics() != null && x.getSchematics().contains("rda.dataset.distribution.license.start_date")).toList(); if(this.conventionService.isListNullOrEmpty(licStartEntities)) { for (FieldEntity licStartDateNode : licStartEntities) { String licStartDateId = licStartDateNode.getId(); Field field = new Field(); field.setKey(licStartDateId); field.setValue(issuedValue); description.getProperties().getFields().add(field); } } } } } if (prefillingMapping.getSemanticTarget().equals("rda.dataset.distribution.available_until") && parsedValue != null && !parsedValue.equals("null")) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd"); LocalDate date = LocalDate.parse(parsedValue, formatter); date = date.plusYears(20); parsedValue = date.toString(); } } List fieldEntities = definition.getAllField().stream().filter(x-> x.getSchematics() != null && x.getSchematics().contains(prefillingMapping.getSemanticTarget())).toList(); for (FieldEntity fieldEntity: fieldEntities) { if (description.getProperties() == null) description.setProperties(new PropertyDefinition()); if (description.getProperties().getFields() == null) description.getProperties().setFields(new ArrayList<>()); description.getProperties().getFields().add(buildFieldValue(fieldEntity, parsedValue, parsedValues, type)); } } } private void applyValueToDescriptionObject(Description description, PrefillingMapping prefillingMapping, String parsedValue, List parsedValues){ switch (prefillingMapping.getTarget()){ case Description._description:{ description.setDescription(parsedValue); } case Description._label:{ description.setLabel(parsedValue); } case Description._descriptionTags:{ if (!parsedValues.isEmpty()){ for (String tagString : parsedValues){ if (description.getDescriptionTags() == null) description.setDescriptionTags(new ArrayList<>()); if(description.getDescriptionTags().stream().anyMatch(x-> x.getTag() !=null && x.getTag().getLabel().equals(tagString))) continue; DescriptionTag descriptionTag = new DescriptionTag(); Tag tag = new Tag(); tag.setLabel(tagString.trim()); descriptionTag.setTag(tag); description.getDescriptionTags().add(descriptionTag); } } } } } private Field buildFieldValue(FieldEntity fieldEntity, String parsedValue, List parsedValues, String type){ String id = fieldEntity.getId(); Field field = new Field(); field.setKey(id); switch (fieldEntity.getData().getFieldType()) { case COMBO_BOX: case AUTO_COMPLETE: case WORD_LIST: { if (!parsedValues.stream().allMatch(Objects::isNull)) { field.setValue(this.jsonHandlingService.toJsonSafe(parseComboBoxValues(fieldEntity, parsedValues))); } break; } case TAGS: { if (!parsedValues.isEmpty()) { field.setValue(jsonHandlingService.toJsonSafe(parseTags(String.join(", ", parsedValues)))); } break; } case DATASET_IDENTIFIER: { JSONObject datasetID = new JSONObject(); datasetID.put("identifier", parsedValue); if (type.equals(PrefillingServiceImpl.Zenodo)) { datasetID.put("type", "doi"); } field.setValue(datasetID.toString()); break; } case LICENSES: { List licenses = this.queryFactory.query(ReferenceQuery.class).references(parsedValues).collect(); LabelAndMultiplicityDataEntity wordListDataEntity = (LabelAndMultiplicityDataEntity) fieldEntity.getData(); if (licenses != null && !licenses.isEmpty() && wordListDataEntity != null) { boolean isMultiAutocomplete = wordListDataEntity.getMultiAutoComplete(); List licenseModels = this.builderFactory.builder(ReferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(new BaseFieldSet(), licenses); if (isMultiAutocomplete) { field.setValue(jsonHandlingService.toJsonSafe(licenseModels)); } else { field.setValue(jsonHandlingService.toJsonSafe(licenseModels.getFirst())); } } break; } default: field.setValue(parsedValue); break; } return field; } private List getValueAsStringArray(PrefillingMapping prefillingMapping, Object value){ String trimRegex = !this.conventionService.isNullOrEmpty(prefillingMapping.getTrimRegex()) ? prefillingMapping.getTrimRegex() : ""; List parsedValues = new ArrayList<>(); if (value instanceof String){ parsedValues.add(((String) value).replace(trimRegex, "")); } else if (value.getClass().isArray()){ if (value instanceof String[]){ List values = new LinkedList<>(); for (String val : (String[]) value) { parsedValues.add(val.replace(trimRegex, "")); } } else { if (prefillingMapping.getSubSource() == null || prefillingMapping.getSubSource().isEmpty()) { throw new MyApplicationException("Source value is an array but no subSource field have been set"); } for (Object obj : (Object[]) value) { if (obj instanceof Map) { Object property = ((Map)obj).getOrDefault(prefillingMapping.getSubSource(), null); if (property instanceof String) { parsedValues.add(((String) property).replaceAll(trimRegex, "")); } } } } } parsedValues = parsedValues.stream().distinct().collect(Collectors.toList()); return parsedValues; } private String getValueAsString(PrefillingMapping prefillingMapping, Object value){ List parsedValues = this.getValueAsStringArray(prefillingMapping, value); return parsedValues.isEmpty() ? null : String.join(", ", parsedValues); } private Object parseComboBoxValues(FieldEntity fieldEntity, List parsedValues) { List normalizedValues = new ArrayList<>(); boolean isMultiSelect; if(fieldEntity.getData().getFieldType().equals(FieldType.AUTO_COMPLETE)) { AutoCompleteDataEntity autoCompleteData = (AutoCompleteDataEntity)fieldEntity.getData(); isMultiSelect = autoCompleteData.getMultiAutoComplete(); for (String format : parsedValues) { List result = new ArrayList<>(); try { result = this.getAutocomplete(autoCompleteData, format); } catch (Exception e) { logger.error(e.getMessage(), e); } result = result.stream().filter(StreamDistinctBy.distinctByKey(ExternalAutocompleteFieldResult::getId)).collect(Collectors.toList()); if(!result.isEmpty()){ List tempValues = new LinkedList<>(); for (ExternalAutocompleteFieldResult f : result) { if (format.equals(f.getId()) || f.getLabel().toUpperCase(Locale.ROOT).contains(format.toUpperCase(Locale.ROOT))) tempValues.add(this.jsonHandlingService.toJsonSafe(f)); } if (isMultiSelect) normalizedValues.addAll(tempValues); else if (!tempValues.isEmpty()) normalizedValues.add(tempValues.get(0)); } } return !normalizedValues.isEmpty() ? (isMultiSelect ? normalizedValues : normalizedValues.getFirst()) : null; } else { WordListDataEntity wordListDataEntity = (WordListDataEntity)fieldEntity.getData(); isMultiSelect = wordListDataEntity.getMultiList(); if (wordListDataEntity.getOptions() != null) { for (ComboBoxDataEntity.Option option : wordListDataEntity.getOptions()) { if (parsedValues.contains(option.getValue())) { normalizedValues.add(option.getValue()); } } } List normalizedStringValues = normalizedValues.stream().map(Object::toString).collect(Collectors.toList()); return !normalizedValues.isEmpty() ? (isMultiSelect ? String.join(", ", normalizedStringValues) : normalizedValues.getFirst()) : null; } } private static List parseTags(String value) { if (value == null || value.isEmpty()) return new LinkedList<>(); String[] rawTags = value.split(","); List parsedTags = new LinkedList<>(); for (String rawTag : rawTags) { Tag tag = new Tag(); tag.setLabel(rawTag.trim()); parsedTags.add(tag); } return parsedTags; } public List getAutocomplete(AutoCompleteDataEntity data, String like) throws URISyntaxException { List result = new LinkedList<>(); ExternalReferenceCriteria urlCriteria = new ExternalReferenceCriteria(); GeneralUrls genericUrls = new GeneralUrls(); int ordinal = 1; List> rawResults = new ArrayList<>(); genericUrls.setFetchMode(FetchStrategy.FIRST); urlCriteria.setLike(like); for (AutoCompleteDataEntity.AutoCompleteSingleData singleData : data.getAutoCompleteSingleDataList()) { UrlConfiguration urlConfiguration = new UrlConfiguration(); try { URI uri; if (singleData.getUrl().contains("?")) { uri = new URI(singleData.getUrl().substring(0, singleData.getUrl().lastIndexOf("?"))); } else { uri = new URI(singleData.getUrl()); } String source = singleData.getAutoCompleteOptions().getSource(); source = source != null && !source.isEmpty() ? source : uri.getHost(); String uriString = singleData.getAutoCompleteOptions().getUri(); uriString = uriString != null && !uriString.isEmpty() ? uriString : "uri"; String parsedUrl = singleData.getUrl(); parsedUrl = parsedUrl.replace("%20", " "); parsedUrl = parsedUrl.replace("%22", "\""); while (parsedUrl.contains("&")) { parsedUrl = parsedUrl.replace("&", "&"); } urlConfiguration.setUrl(parsedUrl); urlConfiguration.setOrdinal(ordinal); urlConfiguration.setType("External"); urlConfiguration.setContentType(MediaType.APPLICATION_JSON_VALUE); urlConfiguration.setFirstpage("1"); urlConfiguration.setRequestType(singleData.getMethod() != null ? singleData.getMethod() : "GET"); DataUrlConfiguration dataUrlConfiguration = new DataUrlConfiguration(); dataUrlConfiguration.setPath(singleData.getOptionsRoot()); DataFieldsUrlConfiguration fieldsUrlConfiguration = new DataFieldsUrlConfiguration(); fieldsUrlConfiguration.setId(singleData.getAutoCompleteOptions().getValue()); fieldsUrlConfiguration.setName(singleData.getAutoCompleteOptions().getLabel()); fieldsUrlConfiguration.setSource(singleData.getAutoCompleteOptions().getSource().isEmpty()? null : singleData.getAutoCompleteOptions().getSource()); fieldsUrlConfiguration.setUri(uriString); dataUrlConfiguration.setFieldsUrlConfiguration(fieldsUrlConfiguration); urlConfiguration.setKey(source); urlConfiguration.setLabel(source); urlConfiguration.setData(dataUrlConfiguration); if (singleData.getHasAuth()) { AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); authenticationConfiguration.setAuthUrl(singleData.getAuth().getUrl()); authenticationConfiguration.setAuthMethod(singleData.getAuth().getMethod()); authenticationConfiguration.setAuthTokenPath(singleData.getAuth().getPath()); authenticationConfiguration.setAuthRequestBody(singleData.getAuth().getBody()); authenticationConfiguration.setType(singleData.getAuth().getType()); urlConfiguration.setAuth(authenticationConfiguration); } genericUrls.getUrls().add(urlConfiguration); List> singleResults = this.remoteFetcherService.getExternalGeneric(urlCriteria, genericUrls); if (!singleResults.isEmpty() && !singleResults.get(0).containsKey("source") && !singleData.getAutoCompleteOptions().getSource().isEmpty()) { singleResults.forEach(singleResult -> singleResult.put("source", singleData.getAutoCompleteOptions().getSource())); } rawResults.addAll(singleResults); genericUrls.getUrls().clear(); } catch (URISyntaxException e) { logger.error(e.getMessage(), e); } } rawResults.forEach(item -> result.add(new ExternalAutocompleteFieldResult(parseItem(item.get("pid")), parseItem(item.get("name")), parseItem(item.get("source")), parseItem(item.get("uri"))))); return result; } private static String parseItem(Object item) { if (item instanceof String) { return (String) item; } if (item instanceof List) { List listedItems = (List) item; return parseItem(listedItems.get(0)); } if (item instanceof Map) { return String.valueOf(((Map)item).get("$")); } return item != null ? item.toString() : null; } //endregion Description Mapping }