AttachmentAspect.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.component.repository;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;

import javax.persistence.EntityManager;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.ReferencedRepositoryFileException;
import org.genesys.filerepository.model.RepositoryFile;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys.filerepository.persistence.RepositoryFilePersistence;
import org.genesys.filerepository.service.RepositoryService;
import org.gringlobal.model.IAttachment;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Remove attachments from the repository
 * 
 * @author Matija Obreza
 * @author Maxym Borodenko
 */
@Aspect
@Component
@Slf4j
public class AttachmentAspect {

	@Autowired
	private RepositoryService repositoryService;

	@Autowired
	private EntityManager entityManager;

	@Autowired
	private RepositoryFilePersistence repositoryFilePersistence;

	/**
	 * Update file reference of any {@link IAttachment} entity on save.
	 *
	 * @param joinPoint the join point
	 * @param attachment the attachment
	 * @return the object
	 * @throws Throwable the throwable
	 */
	@Around(value = "(execution(* org.springframework.data.repository.*.save(..)) || execution(* org.springframework.data.repository.*.saveAndFlush(..)) || execution(* org.springframework.data.jpa.repository.*.save(..))) && args(attachment)")
	public Object aroundAttachmentSave(final ProceedingJoinPoint joinPoint, final IAttachment attachment) throws Throwable {

		log.trace("One attachment is being saved");
		try {
			assignRepositoryFileRef(attachment);
		} catch (final Throwable e) {
			log.error("Failed to set reference to repository file: {}", e.getMessage(), e);
		}

		return joinPoint.proceed();
	}

	/**
	 * Around attachments save checks if IAttachment types are being persisted and updates the file references.
	 *
	 * @param joinPoint the join point
	 * @param genericCollection the generic collection
	 * @return the method result
	 */
	@Around(value = "execution(* org.springframework.data.repository.*.saveAll(..)) && args(genericCollection)")
	public Object aroundAttachmentsSave(final ProceedingJoinPoint joinPoint, final Collection<?> genericCollection) throws Throwable {

		if (genericCollection != null) {
			log.trace("Checking if {} attachments are being saved", genericCollection.size());
			try {
				for (Object element : genericCollection) {
					if (element instanceof IAttachment) {
						assignRepositoryFileRef((IAttachment) element);
					}
				}
			} catch (final Throwable e) {
				log.error("Failed to set reference to repository file: {}", e.getMessage(), e);
			}
		}

		return joinPoint.proceed();
	}

	private void assignRepositoryFileRef(IAttachment attachment) {
		if (attachment == null) {
			return;
		} else if (attachment.getRepositoryFile() != null) {
			return;
		}

		Path attachedFile = Paths.get("/", attachment.getVirtualPath());

		try {
			RepositoryFile repositoryFile = repositoryService.getFile(attachedFile.getParent(), attachedFile.getFileName().toString());
			attachment.setRepositoryFile(repositoryFile);

			log.info("Repository file {} added to attachment", attachedFile);

		} catch (NoSuchRepositoryFileException | InvalidRepositoryPathException e) {
			log.warn("Non-existent file path {}: {}", attachedFile, e.getMessage());
		} catch (Throwable e) {
			log.error("Could not assign repository file {}: {}", attachedFile, e.getMessage(), e);
		}
	}


	/**
	 * Remove repository file when {@link IAttachment} is deleted.
	 *
	 * @param joinPoint the join point
	 * @param attachment the attachment
	 * @return the object
	 * @throws Throwable the throwable
	 */
	@Around(value = "(execution(* org.springframework.data.repository.*.delete(..)) || execution(* org.springframework.data.jpa.repository.*.delete(..))) && args(attachment)")
	public Object aroundAttachmentDelete(final ProceedingJoinPoint joinPoint, final IAttachment attachment) throws Throwable {

		Object result = joinPoint.proceed();

		log.trace("One attachment is being deleted");
		try {
			removeAttachment(attachment);
		} catch (final IOException e) {
			log.error("Failed to remove repository file: {}", e.getMessage(), e);
		}

		return result;
	}

	/**
	 * Remove repository file if an {@link IAttachment} is removed.
	 *
	 * @param joinPoint the join point
	 * @param genericCollection the generic collection
	 * @return the object
	 * @throws Throwable the throwable
	 */
	@Around(value = "(execution(* org.springframework.data.repository.*.deleteAll(..)) || execution(* org.springframework.data.repository.*.deleteAllInBatch(..)) || execution(* org.springframework.data.jpa.repository.*.deleteAll(..)) || execution(* org.springframework.data.jpa.repository.*.deleteAllInBatch(..))) && args(genericCollection)")
	public Object aroundAttachmentsDelete(final ProceedingJoinPoint joinPoint, final Collection<?> genericCollection) throws Throwable {

		Object result = joinPoint.proceed();

		if (genericCollection != null) {
			log.trace("Checking if {} attachments are being deleted", genericCollection.size());
			for (Object element : genericCollection) {
				if (element instanceof IAttachment) {
					try {
						removeAttachment((IAttachment) element);
					} catch (final IOException e) {
						log.error("Failed to remove repository file: {}", e.getMessage(), e);
					}
				}
			}
		}

		return result;
	}

	/**
	 * Remove attachment from file repository
	 * 
	 * @param attachment the attachment to remove
	 * @throws IOException
	 */
	private void removeAttachment(IAttachment attachment) throws IOException {
		if (attachment == null) {
			return;
		}

		Path attachedFile = Paths.get("/", attachment.getVirtualPath());

		entityManager.flush(); // Delete *Attach entity

		try {
			log.debug("Need to remove {}", attachedFile);
			RepositoryFile repositoryFile = (RepositoryFile) Hibernate.unproxy(repositoryFilePersistence.findByUuid(attachment.getRepositoryFile().getUuid()));
			if (repositoryFile instanceof RepositoryImage) {
				repositoryService.removeFileIfPossible((RepositoryImage) repositoryFile);
			} else {
				repositoryService.removeFileIfPossible(repositoryFile);
			}
			
			log.debug("Removed {} from repository", repositoryFile);

		} catch (NoSuchRepositoryFileException e) {
			log.info("Can't remove file from path because it's no longer available {}: {}", attachedFile, e.getMessage());
		} catch (ReferencedRepositoryFileException e) {
			log.info("Can't remove file from path because it's referenced by another entity {}: {}", attachedFile, e.getMessage());
		}
	}
}