RepositoryApiServiceImpl.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.gringlobal.api.v2.facade.impl;
import lombok.extern.slf4j.Slf4j;
import org.genesys.filerepository.FileRepositoryException;
import org.genesys.filerepository.FolderNotEmptyException;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.model.ImageGallery;
import org.genesys.filerepository.model.RepositoryFile;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys.filerepository.service.ImageGalleryService;
import org.genesys.filerepository.service.RepositoryService;
import org.gringlobal.api.Pagination;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.exception.NotFoundElement;
import org.gringlobal.api.model.ImageGalleryDTO;
import org.gringlobal.api.model.RepositoryFileDTO;
import org.gringlobal.api.model.RepositoryFolderDTO;
import org.gringlobal.api.v2.facade.RepositoryApiService;
import org.gringlobal.api.v2.mapper.MapstructMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
@Service
@Transactional(readOnly = true)
@Slf4j
public class RepositoryApiServiceImpl implements RepositoryApiService {
/** The repository service. */
@Autowired
private RepositoryService repositoryService;
@Autowired
private MapstructMapper mapper;
@Autowired
private ImageGalleryService imageGalleryService;
@Override
public void downloadFile(UUID fileUuid, HttpServletRequest request, HttpServletResponse response) throws IOException, NoSuchRepositoryFileException {
final RepositoryFile repositoryFile = repositoryService.getFile(fileUuid);
String eTag = repositoryFile.getSha1Sum();
if (eTag.equals(request.getHeader(HttpHeaders.IF_NONE_MATCH))) {
response.setStatus(HttpStatus.NOT_MODIFIED.value());
response.flushBuffer();
return;
}
long sinceDate = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (sinceDate >= -1 && repositoryFile.getLastModifiedDate().getEpochSecond() < sinceDate) {
response.setStatus(HttpStatus.NOT_MODIFIED.value());
response.flushBuffer();
return;
}
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=86400, s-maxage=86400, public, no-transform");
response.setHeader(HttpHeaders.PRAGMA, "");
response.setDateHeader(HttpHeaders.LAST_MODIFIED, repositoryFile.getLastModifiedDate().getEpochSecond());
response.setHeader(HttpHeaders.ETAG, eTag);
response.setContentType(repositoryFile.getContentType());
response.addHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", repositoryFile.getOriginalFilename()));
response.setContentLength(repositoryFile.getSize());
repositoryService.streamFileBytes(repositoryFile, response.getOutputStream());
response.flushBuffer();
}
@Override
public void downloadFolderAsZip(UUID folderUuid, HttpServletResponse response) throws IOException, InvalidRepositoryPathException {
final RepositoryFolder repositoryFolder = repositoryService.getFolder(folderUuid);
response.setContentType("application/zip");
response.addHeader("Content-Disposition", String.format("attachment; filename=\"%s.zip\"", repositoryFolder.getName()));
repositoryService.getFolderAsZip(repositoryFolder, response.getOutputStream());
response.flushBuffer();
}
@Override
@Transactional(rollbackFor = { Throwable.class, IOException.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T addFile(Path repositoryPath, String originalFilename, String contentType, byte[] bytes, T metaData) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return (T) mapper.map(repositoryService.addFile(repositoryPath, originalFilename, contentType, bytes, mapper.map(metaData)));
}
@Override
@Transactional(rollbackFor = { Throwable.class, IOException.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T addFile(Path repositoryPath, String originalFilename, String contentType, File fileWithData, T metaData) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return (T) mapper.map(repositoryService.addFile(repositoryPath, originalFilename, contentType, fileWithData, mapper.map(metaData)));
}
@Override
@Transactional(rollbackFor = { Throwable.class, IOException.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T addFile(Path repositoryPath, String originalFilename, String contentType, InputStream inputStream, T metaData) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return (T) mapper.map(repositoryService.addFile(repositoryPath, originalFilename, contentType, inputStream, mapper.map(metaData)));
}
@Override
public <T extends RepositoryFileDTO> T getFile(UUID fileUuid) throws NoSuchRepositoryFileException {
return (T) mapper.map((RepositoryFile) repositoryService.getFile(fileUuid));
}
@Override
public <T extends RepositoryFileDTO> T getFile(UUID fileUuid, int version) throws NoSuchRepositoryFileException {
return (T) mapper.map((RepositoryFile) repositoryService.getFile(fileUuid, version));
}
@Override
public <T extends RepositoryFileDTO> T getFile(Path path, String filename) throws NoSuchRepositoryFileException, InvalidRepositoryPathException {
return (T) mapper.map((RepositoryFile) repositoryService.getFile(path, filename));
}
@Override
@Transactional(rollbackFor = { Throwable.class, IOException.class, FileRepositoryException.class })
public List<RepositoryFileDTO> extractZip(UUID fileUuid) throws InvalidRepositoryFileDataException, IOException, InvalidRepositoryPathException, NoSuchRepositoryFileException {
return mapper.map(repositoryService.extractZip(repositoryService.getFile(fileUuid)), mapper::map);
}
@Override
public Page<RepositoryFileDTO> listFiles(Path folderPath, Pageable page) throws InvalidRepositoryPathException {
return mapper.map(repositoryService.listFiles(folderPath, page), mapper::map);
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T updateMetadata(T repositoryFileDTO) throws NoSuchRepositoryFileException {
return (T) mapper.map(repositoryService.updateMetadata(mapper.map(repositoryFileDTO)));
}
@Override
@Transactional(rollbackFor = { Throwable.class, IOException.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T removeFile(UUID fileUuid) throws NoSuchRepositoryFileException, IOException {
return (T) mapper.map((RepositoryFile) repositoryService.removeFile(repositoryService.getFile(fileUuid)));
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public <T extends RepositoryFileDTO> T moveAndRenameFile(T repositoryFileDTO, Path fullPath) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException {
return (T) mapper.map(repositoryService.moveAndRenameFile(mapper.map(repositoryFileDTO), fullPath));
}
@Override
public RepositoryFolderDTO getFolder(UUID uuid) {
return mapper.map(repositoryService.getFolder(uuid));
}
@Override
public RepositoryFolderDTO getFolder(Path folderPath) throws InvalidRepositoryPathException {
return mapper.map(repositoryService.getFolder(folderPath));
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public RepositoryFolderDTO updateFolder(RepositoryFolderDTO folder) {
try {
return mapper.map(repositoryService.updateFolder(mapper.map(folder)));
} catch (Throwable e) {
throw new InvalidApiUsageException(e);
}
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public RepositoryFolderDTO ensureFolder(Path path) throws InvalidRepositoryPathException {
return mapper.map(repositoryService.ensureFolder(path));
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public RepositoryFolderDTO deleteFolder(Path path) throws FolderNotEmptyException, InvalidRepositoryPathException {
return mapper.map(repositoryService.deleteFolder(path));
}
@Override
public Page<RepositoryFolderDTO> listFolders(Path root, Pageable page) throws InvalidRepositoryPathException {
return mapper.map(repositoryService.listFolders(root, page), mapper::map);
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public FolderDetails renameFolder(UUID folderUuid, String fullPath) throws InvalidRepositoryPathException {
RepositoryFolder folder = repositoryService.getFolder(folderUuid);
if (folder == null) {
throw new NotFoundElement("No folder with uuid=" + folderUuid);
}
return folderDetails(repositoryService.renamePath(folder.getFolderPath(), Paths.get(fullPath)).getFolderPath());
}
/**
* Folder details.
*
* @param path the path
* @return the folder details
* @throws InvalidRepositoryPathException the invalid repository path exception
*/
@Override
public FolderDetails folderDetails(final Path path) throws InvalidRepositoryPathException {
FolderDetails fd = new FolderDetails();
fd.folder = getFolder(path);
fd.subFolders = listFolders(path, Pagination.toPageRequest(50, RepositoryFolder.DEFAULT_SORT));
if (fd.folder == null && !path.toAbsolutePath().toString().equals("/")) {
throw new NotFoundElement("No such folder");
}
fd.files = listFiles(path, Pagination.toPageRequest(50, RepositoryFile.DEFAULT_SORT));
fd.gallery = mapper.map(imageGalleryService.loadImageGallery(path));
return fd;
}
@Override
public ImageGalleryDTO getGallery(Path path) throws InvalidRepositoryPathException {
return mapper.map(imageGalleryService.loadImageGallery(path));
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public ImageGalleryDTO createImageGallery(Path path, String title, String description) throws InvalidRepositoryPathException {
return mapper.map(imageGalleryService.createImageGallery(path, title, description));
}
@Override
@Transactional(rollbackFor = { Throwable.class, FileRepositoryException.class })
public ImageGalleryDTO removeGallery(Path path) throws InvalidRepositoryPathException {
ImageGallery imageGallery = imageGalleryService.loadImageGallery(path);
imageGalleryService.removeGallery(imageGallery);
return mapper.map(imageGallery);
}
@Override
public RepositoryFileDTO getMetadata(String path, String uuid, String ext) throws NoSuchRepositoryFileException {
final RepositoryFile repositoryFile = this.repositoryService.getFile(UUID.fromString(uuid));
sanityCheck(Paths.get(path), ext, repositoryFile);
return mapper.map(repositoryFile);
}
@Override
public void sanityCheck(final Path path, final String ext, final RepositoryFile repositoryFile) {
if (repositoryFile == null) {
throw new NotFoundElement("No such thing");
}
if (!repositoryFile.getStorageFolder().equals(path.toString()) || !repositoryFile.getExtension().equals(ext)) {
log.warn("{}!={}", repositoryFile.getStorageFolder(), path);
log.warn("{}!={}", repositoryFile.getExtension(), ext);
throw new NotFoundElement("No such thing");
}
}
}