CropServiceImpl.java

/*
 * Copyright 2020 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.gringlobal.service.impl;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.exception.NotFoundElement;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Crop;
import org.gringlobal.model.CropAttach;
import org.gringlobal.model.QCrop;
import org.gringlobal.model.QCropAttach;
import org.gringlobal.model.QTaxonomyCropMap;
import org.gringlobal.model.QTaxonomySpecies;
import org.gringlobal.model.TaxonomyCropMap;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.persistence.CropRepository;
import org.gringlobal.persistence.TaxonomyCropMapRepository;
import org.gringlobal.persistence.TaxonomySpeciesRepository;
import org.gringlobal.service.CropAttachmentService;
import org.gringlobal.service.CropAttachmentService.CropAttachmentRequest;
import org.gringlobal.service.CropService;
import org.gringlobal.service.filter.CropFilter;
import org.gringlobal.service.filter.TaxonomySpeciesFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;

/**
 * The Class CropServiceImpl.
 */
@Service
@Transactional(readOnly = true)
@Validated
@Slf4j
public class CropServiceImpl extends FilteredCRUDServiceImpl<Crop, CropFilter, CropRepository> implements CropService {

	@Autowired
	private TaxonomyCropMapRepository taxonomyCropMapRepository;

	@Autowired
	private TaxonomySpeciesRepository taxonomySpeciesRepository;

	@Component
	protected static class AttachmentSupport extends BaseAttachmentSupport<Crop, CropAttach, CropAttachmentRequest> implements CropAttachmentService {

		public AttachmentSupport() {
			super(QCropAttach.cropAttach.crop().id, QCropAttach.cropAttach.id);
		}

		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		public CropAttach uploadFile(Crop entity, MultipartFile file, CropAttachmentRequest metadata) throws IOException, InvalidRepositoryPathException, InvalidRepositoryFileDataException {
			return super.uploadFile(entity, file, metadata);
		}

		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		public CropAttach removeFile(Crop entity, Long attachmentId) {
			return super.removeFile(entity, attachmentId);
		}

		@Override
		protected Path createRepositoryPath(Crop crop) {
			crop = owningEntityRepository.getReferenceById(crop.getId());
			return Paths.get("/crop/" + crop.getName());
		}

		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		protected CropAttach createAttach(Crop entity, CropAttach source) {
			CropAttach attach = new CropAttach();
			attach.apply(source);
			attach.setVirtualPath(source.getVirtualPath()); // SOAP uses this to create the record
			attach.setCrop(entity);
			return attach;
		}

		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		public CropAttach create(CropAttach source) {
			var owningEntity = owningEntityRepository.getReferenceById(source.getCrop().getId());

			var attach = createAttach(owningEntity, source);
			var savedAttach = repository.save(attach);
			return _lazyLoad(savedAttach);
		}

		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		public CropAttach update(CropAttach updated, CropAttach target) {
			target.apply(updated);
			return _lazyLoad(repository.save(target));
		}
		
		@Override
		@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
		public CropAttach remove(CropAttach entity) {
			return super.remove(entity);
		}
	}
	
	@Override
	public Crop getCrop(String cropName) {
		return repository.getByName(cropName);
	}

	@Override
	public CropDetails getCropDetails(Crop crop) {
		if (crop == null) {
			throw new NotFoundElement();
		}

		crop = this.reload(crop);

		// initialize lazy data
		if (crop.getAttachments() != null) {
			crop.getAttachments().size();
			crop.getAttachments().forEach(a -> {
				if (a.getRepositoryFile() != null) {
					a.getRepositoryFile().getId();
				}
				if (a.getAttachCooperator() != null) {
					a.getAttachCooperator().getId();
				}
			});
		}

		CropDetails cropDetails = new CropDetails();
		cropDetails.crop = crop;
		cropDetails.attachments = crop.getAttachments();

		return cropDetails;
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'ADMINISTRATION')")
	public List<TaxonomyCropMap> addSpecies(Crop crop, List<TaxonomySpecies> species, String note) {
		if (CollectionUtils.isEmpty(species)) {
			return Collections.emptyList();
		}

		final var loadedCrop = get(crop);
		List<TaxonomyCropMap> taxCropMaps = species.stream().map((taxonomySpecies) -> {
			TaxonomyCropMap taxonomyCropMap = new TaxonomyCropMap();
			taxonomyCropMap.setCrop(loadedCrop);
			taxonomyCropMap.setAlternateCropName(loadedCrop.getName());
			taxonomyCropMap.setTaxonomySpecies(taxonomySpecies);
			taxonomyCropMap.setNote(note);
			return taxonomyCropMap;
		}).collect(Collectors.toList());
		List<TaxonomyCropMap> savedCropMaps = taxonomyCropMapRepository.saveAll(taxCropMaps);
		log.debug("Added {} species to crop {}", savedCropMaps.size(), loadedCrop.getName());

		return savedCropMaps;
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'ADMINISTRATION')")
	public List<TaxonomyCropMap> removeSpecies(Crop crop, List<TaxonomySpecies> species) {
		if (CollectionUtils.isEmpty(species)) {
			return Collections.emptyList();
		}
		final var loadedCrop = get(crop);

		List<TaxonomyCropMap> mapsForRemove = taxonomyCropMapRepository.findAllByCropAndTaxonomySpeciesIn(crop, species);

		taxonomyCropMapRepository.deleteAll(mapsForRemove);
		log.debug("Removed {} species from crop {}", mapsForRemove.size(), loadedCrop.getName());
		return mapsForRemove;
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'CREATE')")
	public Crop create(final Crop source) {
		log.debug("Create Crop. Input data {}", source);
		Crop crop = new Crop();
		crop.apply(source);

		Crop saved = repository.save(crop);
		saved.lazyLoad();

		return saved;
	}

	@Override
	@Transactional
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'WRITE')")
	public Crop update(final Crop input, Crop target) {
		log.debug("Update Crop. Input data {}", input);
		target.apply(input);

		Crop saved = repository.save(target);
		saved.lazyLoad();
		return saved;
	}

	@Override
	@PreAuthorize("hasAuthority('GROUP_ADMINS') or @ggceSec.actionAllowed('Crop', 'DELETE')")
	// This method is overridden because we need permission check
	public Crop remove(Crop entity) {
		return super.remove(entity);
	}

	@Override
	@Transactional
	public TaxonomyCropMap ensureCropTaxonomyLink(Crop crop, TaxonomySpecies taxonomySpecies) {
		TaxonomyCropMap taxonomyCropMap = taxonomyCropMapRepository.findOne(QTaxonomyCropMap.taxonomyCropMap.crop().eq(crop).and(QTaxonomyCropMap.taxonomyCropMap.taxonomySpecies().eq(taxonomySpecies))).orElse(null);
		if (taxonomyCropMap == null) {
			taxonomyCropMap = new TaxonomyCropMap();
			taxonomyCropMap.setCrop(crop);
			taxonomyCropMap.setTaxonomySpecies(taxonomySpecies);
			taxonomyCropMap.setAlternateCropName(crop.getName());
			taxonomyCropMapRepository.save(taxonomyCropMap);
		}
		return taxonomyCropMap;
	}

	@Override
	public Page<Crop> list(CropFilter filter, Pageable page) throws SearchException {
		return super.list(Crop.class, filter, page);
	}

	/**
	 * List {@link TaxonomySpecies} associated with the crop.
	 *
	 * @param crop the crop
	 * @param filter the filter
	 * @param pageable the pageable
	 * @return the page
	 */
	@Override
	public Page<TaxonomySpecies> listSpecies(Crop crop, TaxonomySpeciesFilter filter, Pageable pageable) {

		QTaxonomySpecies species = new QTaxonomySpecies("taxonomySpecies"); // alias for filtering
		JPAQuery<TaxonomySpecies> query = jpaQueryFactory.from(QTaxonomyCropMap.taxonomyCropMap).innerJoin(QTaxonomyCropMap.taxonomyCropMap.taxonomySpecies(), species).select(species);
		query.where(QTaxonomyCropMap.taxonomyCropMap.crop().eq(crop));
		query.where(ExpressionUtils.allOf(filter.collectPredicates(species)));

		return taxonomySpeciesRepository.findAll(query, pageable);
	}

	@Override
	public List<Crop> autocompleteCrops(String term) {
		if (StringUtils.isBlank(term) || term.length() < 1) {
			return Collections.emptyList();
		}
		BooleanExpression expression = QCrop.crop.name.startsWithIgnoreCase(term);
		return repository.findAll(expression, PageRequest.of(0, 15, Sort.by("name"))).getContent();
	}

	@Override
	public Page<Crop> listCrops(Pageable pageable) {
		return repository.findAll(pageable);
	}
}