ImageGalleryServiceImpl.java

/*
 * Copyright 2018 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.filerepository.service.impl;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.persistence.EntityNotFoundException;

import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.model.ImageGallery;
import org.genesys.filerepository.model.QImageGallery;
import org.genesys.filerepository.model.QRepositoryFolder;
import org.genesys.filerepository.model.RepositoryFile;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys.filerepository.persistence.ImageGalleryPersistence;
import org.genesys.filerepository.service.ImageGalleryService;
import org.genesys.filerepository.service.RepositoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * Image Gallery service.
 */
@Service
@Transactional(readOnly = true)
@Slf4j
public class ImageGalleryServiceImpl implements ImageGalleryService {

	/** The document file persistence. */
	@Autowired
	private ImageGalleryPersistence imageGalleryPersistence;

	/** The repository service. */
	@Autowired
	private RepositoryService repositoryService;

	/** The jpa query factory. */
	@Autowired
	private JPAQueryFactory jpaQueryFactory;

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#loadImageGallery(java.
	 * lang.String)
	 */
	@Override
	@PostAuthorize("returnObject == null or hasRole('ADMINISTRATOR') or hasPermission(returnObject, 'read')")
	public ImageGallery loadImageGallery(final Path path) throws InvalidRepositoryPathException {
		PathValidator.checkValidPath(path);
		final ImageGallery imageGallery = imageGalleryPersistence.findByFolder(repositoryService.getFolder(path));
		return deepLoad(imageGallery);
	}

	@Override
	@PostFilter("filterObject==null || hasRole('ADMINISTRATOR') || hasPermission(filterObject, 'read')")
	public List<ImageGallery> loadImageGalleries(final List<Path> paths) throws InvalidRepositoryPathException {
		List<String> normalizedPaths = new ArrayList<>(paths.size());
		for (Path path : paths) {
			PathValidator.checkValidPath(path);
			normalizedPaths.add(path.normalize().toAbsolutePath().toString());
		}

		List<Long> folders = jpaQueryFactory.select(QRepositoryFolder.repositoryFolder.id).from(QRepositoryFolder.repositoryFolder).where(QRepositoryFolder.repositoryFolder.path.in(normalizedPaths)).fetch();

		JPAQuery<ImageGallery> query = jpaQueryFactory.selectFrom(QImageGallery.imageGallery).where(QImageGallery.imageGallery.folder.id.in(folders));
		return query.fetch()
				.stream().map(this::deepLoad).collect(Collectors.toList());
	}
	
	@Override
	@PostAuthorize("returnObject == null or hasRole('ADMINISTRATOR') or hasPermission(returnObject, 'read')")
	public ImageGallery getImageGallery(RepositoryFolder folder) {
		return imageGalleryPersistence.findByFolder(folder);
	}

	/**
	 * Loads {@link ImageGallery#getImages()} when not null.
	 *
	 * @param imageGallery the image gallery
	 * @return the image gallery
	 */
	private ImageGallery deepLoad(final ImageGallery imageGallery) {
		// Fetch image info
		if (imageGallery != null) {
			imageGallery.getImages().size();
		}
		return imageGallery;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#createImageGallery(
	 * java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	@Transactional(rollbackFor = Throwable.class)
	@PreAuthorize("isAuthenticated()")
	public ImageGallery createImageGallery(final Path path, final String title, final String description) throws InvalidRepositoryPathException {
		PathValidator.checkValidPath(path);
		
		if ("/".equals(path.normalize().toAbsolutePath().toString())) {
			throw new InvalidRepositoryPathException("Image gallery cannot be created at " + path);
		}

		log.debug("Creating ImageGallery at path={}", path);

		RepositoryFolder repositoryFolder = repositoryService.ensureFolder(path);

		ImageGallery imageGallery = imageGalleryPersistence.findByFolder(repositoryFolder);
		if (imageGallery != null) {
			return imageGallery;
		}

		if (!SecurityContextUtil.hasRole("ADMINISTRATOR") && !SecurityContextUtil.hasPermission(repositoryFolder, "WRITE")) {
			throw new AccessDeniedException("No WRITE permission on " + repositoryFolder.getPath());
		}

		log.info("Creating new image gallery at path={}", path);
		imageGallery = new ImageGallery();
		imageGallery.setFolder(repositoryFolder);
		imageGallery.setPath(path.toString());
		imageGallery.setTitle(title);
		imageGallery.setDescription(description);

		final List<RepositoryImage> images = new ArrayList<>(repositoryService.listImages(Paths.get(imageGallery.getPath()), RepositoryFile.DEFAULT_SORT));

		imageGallery.setImages(images);

		imageGallery = imageGalleryPersistence.save(imageGallery);
		ensureThumbnails(imageGallery);
		return imageGallery;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#removeGallery(org.
	 * genesys.filerepository. model.ImageGallery)
	 */
	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#imageGallery, 'delete')")
	public ImageGallery removeGallery(final ImageGallery imageGallery) {
		log.debug("Deleting ImageGallery with id={}", imageGallery.getId());

		imageGalleryPersistence.delete(imageGallery);
		return imageGallery;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#updateImageGalery(org.
	 * genesys2.server. filerepository.model.ImageGallery, java.lang.String,
	 * java.lang.String)
	 */
	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#imageGallery, 'write')")
	public ImageGallery updateImageGalery(final ImageGallery imageGallery, final String title, final String description) {
		if (imageGallery.getId() == null) {
			throw new EntityNotFoundException("The given id must not be null");
		}
		final ImageGallery imageGallery2 = imageGalleryPersistence.findById(imageGallery.getId()).orElseThrow(() -> new EntityNotFoundException("Record not found."));

		imageGallery2.setTitle(title);
		imageGallery2.setDescription(description);
		imageGalleryPersistence.save(imageGallery2);

		return deepLoad(imageGallery2);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#saveImageOrder(org.
	 * genesys.filerepository. model.ImageGallery)
	 */
	@Override
	@Transactional
	@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#imageGallery, 'write')")
	public ImageGallery saveImageOrder(final ImageGallery imageGallery) {
		assert (imageGallery != null && imageGallery.getId() != null);
		final ImageGallery imageGallery2 = imageGalleryPersistence.findById(imageGallery.getId()).orElseThrow(() -> new EntityNotFoundException("Record not found."));

		imageGallery2.setImages(imageGallery.getImages());
		imageGalleryPersistence.save(imageGallery2);

		return deepLoad(imageGallery2);
	}
	
	@Override
	public void ensureThumbnails(final ImageGallery imageGallery) {
		assert (imageGallery != null && imageGallery.getId() != null);
		final ImageGallery imageGallery2 = imageGalleryPersistence.findById(imageGallery.getId()).orElseThrow(() -> new EntityNotFoundException("Record not found."));

		if (CollectionUtils.isEmpty(imageGallery2.getImages())) {
			log.debug("ImageGallery has no images, skipping thumbnail generation for path={}", imageGallery2.getPath());
			return;
		}

		imageGallery2.getImages().forEach(repositoryImage -> repositoryService.ensureThumbnails(repositoryImage));
	}

	/**
	 * List image galleries.
	 *
	 * @param pageable the pageable
	 * @return the page
	 */
	@Override
	@PreAuthorize("hasRole('ADMINISTRATOR')")
	public Page<ImageGallery> listImageGalleries(final Pageable pageable) {
		return imageGalleryPersistence.findAll(pageable);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.genesys.filerepository.service.ImageGalleryService#listImageGalleries(
	 * java.lang.String, org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<ImageGallery> listImageGalleries(final Path root, final Pageable pageable) throws InvalidRepositoryPathException {
		PathValidator.checkValidPath(root);
		List<RepositoryFolder> folders = repositoryService.listPathsRecursively(root);
		// Remove folders user can't read
		folders.removeIf(folder -> !SecurityContextUtil.hasRole("ADMINISTRATOR") && !SecurityContextUtil.hasPermission(folder, "READ"));

		JPAQuery<ImageGallery> query = jpaQueryFactory.selectFrom(QImageGallery.imageGallery).where(QImageGallery.imageGallery.folder.in(folders));
		long total = query.fetchCount();

		List<ImageGallery> content = query.join(QImageGallery.imageGallery.folder).fetchJoin()
			// page
			.offset(pageable.getOffset()).limit(pageable.getPageSize())
			// fetch
			.fetch();

		// content.forEach(imageGallery -> imageGallery.getImages().size());
		// content.forEach(imageGallery -> imageGallery.getFolder().getId());
		return new PageImpl<>(content, pageable, total);
	}

}