DescriptorApiServiceImpl.java

/*
 * Copyright 2024 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.genesys.server.api.v2.facade.impl;

import org.apache.commons.collections4.CollectionUtils;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys.server.api.v2.facade.DescriptorApiService;
import org.genesys.server.api.v2.model.dataset.DatasetInfo;
import org.genesys.server.api.v2.model.impl.DescriptorDTO;
import org.genesys.server.api.v2.model.impl.DescriptorLangDTO;
import org.genesys.server.api.v2.model.impl.DescriptorListInfo;
import org.genesys.server.api.v2.model.impl.TranslatedDescriptorDTO;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.filters.DescriptorFilter;
import org.genesys.server.model.traits.Descriptor;
import org.genesys.server.model.traits.DescriptorLang;
import org.genesys.server.service.DescriptorService;
import org.genesys.server.service.DescriptorTranslationService;
import org.genesys.server.service.TranslatorService;
import org.genesys.server.service.VocabularyTermService;
import org.genesys.server.service.TranslatorService.TranslatorException;
import org.genesys.server.service.worker.dupe.DescriptorDuplicateFinder;
import org.genesys.server.service.worker.dupe.DuplicateFinder;
import org.hibernate.validator.internal.util.stereotypes.Lazy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

@Service("descriptorV2APIService")
@Validated
public class DescriptorApiServiceImpl extends APIFilteredTranslatedServiceFacadeImpl<DescriptorService, DescriptorDTO,
	TranslatedDescriptorDTO, DescriptorLangDTO, Descriptor, DescriptorLang,
	DescriptorTranslationService.TranslatedDescriptor, DescriptorFilter> implements DescriptorApiService {

	@Autowired
	private DescriptorDuplicateFinder duplicateFinder;

	@Autowired
	@Lazy
	private VocabularyTermService vocabularyTermSerice;

	@Override
	protected TranslatedDescriptorDTO convertTranslation(DescriptorTranslationService.TranslatedDescriptor source) {
		return mapper.map(source);
	}

	@Override
	protected DescriptorLang convertLang(DescriptorLangDTO source) {
		return mapper.map(source);
	}

	@Override
	protected DescriptorLangDTO convertLang(DescriptorLang source) {
		return mapper.map(source);
	}

	@Override
	protected Descriptor convert(DescriptorDTO source) {
		return mapper.map(source);
	}

	@Override
	protected DescriptorDTO convert(Descriptor source) {
		return mapper.map(source);
	}

	@Override
	@Transactional(readOnly = true)
	public List<DescriptorDTO> searchMatchingDescriptor(DescriptorDTO descriptor) {
		return mapper.map(service.searchMatchingDescriptor(mapper.map(descriptor)), mapper::map);
	}

	@Override
	@Transactional
	public DescriptorDTO updateImage(DescriptorDTO descriptor, MultipartFile file, RepositoryImage imageMetadata) throws IOException, InvalidRepositoryPathException, InvalidRepositoryFileDataException, NoSuchRepositoryFileException {
		return mapper.map(service.updateImage(mapper.map(descriptor), file, imageMetadata));
	}

	@Override
	@Transactional
	public DescriptorDTO removeImage(DescriptorDTO descriptor) throws NoSuchRepositoryFileException, IOException, InvalidRepositoryPathException {
		return mapper.map(service.removeImage(mapper.map(descriptor)));
	}

	@Override
	@Transactional
	public DescriptorDTO forceUpdateDescriptor(DescriptorDTO descriptor) {
		return mapper.map(service.forceUpdateDescriptor(mapper.map(descriptor)));
	}

	@Override
	@Transactional(readOnly = true)
	public DescriptorDTO loadDescriptor(UUID uuid) throws NotFoundElement {
		return mapper.map(service.loadDescriptor(uuid));
	}

	@Override
	@Transactional(readOnly = true)
	public TranslatedDescriptorDTO loadTranslatedDescriptor(UUID uuid) throws NotFoundElement {
		return mapper.map(service.loadTranslatedDescriptor(uuid));
	}

	@Override
	@Transactional(readOnly = true)
	public DescriptorDTO loadDescriptor(UUID uuid, int version) throws NotFoundElement {
		return mapper.map(service.loadDescriptor(uuid, version));
	}

	@Override
	@Transactional(readOnly = true)
	public Page<DescriptorDTO> listDescriptorsForCurrentUser(DescriptorFilter filter, Pageable page) throws IOException, SearchException {
		return mapper.map(service.listDescriptorsForCurrentUser(filter, page), mapper::map);
	}

	@Override
	@Transactional(readOnly = true)
	public Page<DescriptorDTO> listAccessibleDescriptors(DescriptorFilter filter, Pageable page) throws IOException, SearchException {
		return mapper.map(service.listAccessibleDescriptors(filter, page), mapper::map);
	}

	@Override
	@Transactional(readOnly = true)
	public Page<DescriptorDTO> listDescriptors(DescriptorFilter descriptorFilter, Pageable page) throws SearchException {
		return mapper.map(service.listDescriptors(descriptorFilter, page), mapper::map);
	}

	@Override
	@Transactional(readOnly = true)
	public Page<TranslatedDescriptorDTO> listDescriptorsDetails(DescriptorFilter filter, Pageable page) throws SearchException {
		return mapper.map(service.listDescriptorsDetails(filter, page), mapper::map);
	}

	@Override
	@Transactional
	public DescriptorDTO removeDescriptor(DescriptorDTO descriptor) throws InvalidRepositoryPathException, IOException {
		return mapper.map(service.rejectDescriptor(mapper.map(descriptor)));
	}

	@Override
	@Transactional
	public List<DescriptorDTO> upsertDescriptors(List<DescriptorDTO> source) {
		return mapper.map(service.upsertDescriptors(mapper.map(source, mapper::map)), mapper::map);
	}

	@Override
	@Transactional
	public DescriptorDTO upsertDescriptor(DescriptorDTO source) {
		return mapper.map(service.upsertDescriptor(mapper.map(source)));
	}

	@Override
	@Transactional
	public DescriptorDTO approveDescriptor(DescriptorDTO descriptor) {
		return mapper.map(service.approveDescriptor(mapper.map(descriptor)));
	}

	@Override
	@Transactional
	public DescriptorDTO reviewDescriptor(DescriptorDTO descriptor) {
		return mapper.map(service.reviewDescriptor(mapper.map(descriptor)));
	}

	@Override
	@Transactional
	public DescriptorDTO rejectDescriptor(DescriptorDTO descriptor) {
		return mapper.map(service.rejectDescriptor(mapper.map(descriptor)));
	}

	@Override
	@Transactional(readOnly = true)
	public List<DescriptorListInfo> getDescriptorLists(UUID uuid) {
		return mapper.map(service.getDescriptorLists(service.getDescriptor(uuid)), mapper::mapInfo);
	}

	@Override
	@Transactional(readOnly = true)
	public List<DatasetInfo> getDatasets(UUID uuid) {
		return mapper.map(service.getDatasets(service.getDescriptor(uuid)), mapper::mapInfo);
	}

	@Override
	@Transactional
	public DescriptorDTO nextVersion(DescriptorDTO descriptor, boolean major) {
		return mapper.map(service.nextVersion(mapper.map(descriptor), major));
	}

	@Override
	public void exportDescriptors(DescriptorFilter filter, OutputStream outputStream) throws IOException {
		service.exportDescriptors(filter, outputStream);
	}

	@Override
	@Transactional(readOnly = true)
	public long countDescriptors(DescriptorFilter filter) throws SearchException {
		return service.countDescriptors(filter);
	}

	@Override
	@Transactional(readOnly = true)
	public DescriptorDTO getDescriptor(UUID uuid) {
		return mapper.map(service.getDescriptor(uuid));
	}

	@Override
	public DescriptorLangDTO machineTranslate(long entityId, String targetLanguage) throws TranslatorException {
		var descriptor = service.get(entityId);
		var dto = mapper.map(service.machineTranslate(descriptor, targetLanguage));
		dto.setTerms(mapper.map(service.machineTranslateTerms(descriptor, targetLanguage), mapper::map));
		return dto;
	}

	@Override
	@Transactional(readOnly = true)
	public DescriptorLangDTO machineTranslate(UUID uuid, String targetLanguage) throws TranslatorService.TranslatorException {
		var descriptor = service.getDescriptor(uuid);
		var dto = mapper.map(service.machineTranslate(descriptor, targetLanguage));
		dto.setTerms(mapper.map(service.machineTranslateTerms(descriptor, targetLanguage), mapper::map));
		return dto;
	}

	@Override
	public DescriptorLangDTO upsertTranslation(DescriptorDTO entity, DescriptorLangDTO input) {
		var dto = super.upsertTranslation(entity, input);
		if (CollectionUtils.isNotEmpty(input.getTerms())) {
			var descriptor = service.get(dto.getEntityId());
			if (CollectionUtils.isEmpty(descriptor.getTerms())) {
				throw new InvalidApiUsageException("Descriptor does not have terms!");
			}
			dto.setTerms(new LinkedList<>());
			for (var dt : descriptor.getTerms()) {
				var it = input.getTerms().stream().filter(t -> Objects.equals(t.getEntityId(), dt.getId())).findFirst().orElse(null);
				if (it != null) {
					var tdt = vocabularyTermSerice.upsertTranslation(dt, mapper.map(it));
					dto.getTerms().add(mapper.map(tdt));
				}
			}
		}
		return dto;
	}

	@Override
	@Transactional(readOnly = true)
	public List<DuplicateFinder.Hit<DescriptorDTO>> findSimilar(SimilarRequest similarRequest) {
		DescriptorDTO target = similarRequest.select;
		if (target.getUuid() != null) {
			target = loadDescriptor(target.getUuid());
		}
		var result = duplicateFinder.findSimilar(mapper.map(target), similarRequest.target);
		return result.stream().map(hit -> {
			var dtoHit = new DuplicateFinder.Hit<>(mapper.map(hit.result), hit.score);
			dtoHit.hitRating = hit.hitRating;
			dtoHit.matches = hit.matches;
			return dtoHit;
		}).collect(Collectors.toList());
	}

	@Override
	@Transactional(readOnly = true)
	public DescriptorDetails getDescriptorDetails(UUID uuid) {
		DescriptorDetails details = new DescriptorDetails();
		details.descriptor = mapper.map(service.loadTranslatedDescriptor(uuid));
		details.descriptorLists = getDescriptorLists(uuid);
		details.datasets = getDatasets(uuid);
		return details;
	}

}