Commit 2a51bef3 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch 'ftp-server-work' into 'master'

Ftp server work

See merge request !11
parents e5941740 77cc81e5
......@@ -63,7 +63,7 @@ public class ImageGallery extends AuditedVersionedModel implements Copyable<Imag
private String description;
/** The images. */
@ManyToMany(cascade = { CascadeType.REMOVE, CascadeType.REFRESH })
@ManyToMany(cascade = { CascadeType.REFRESH })
@JoinTable(name = "repositorygalleryimage", joinColumns = @JoinColumn(name = "galleryId"), inverseJoinColumns = @JoinColumn(name = "imageId"))
@OrderColumn(name = "position")
private List<RepositoryImage> images;
......
......@@ -173,9 +173,7 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements En
*/
@Transient
public String getUrl() {
final StringBuffer sb = new StringBuffer();
sb.append(path).append(getFilename());
return sb.toString();
return getStorageFullPath();
}
/**
......
......@@ -16,6 +16,7 @@
package org.genesys.filerepository.persistence;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
......@@ -49,7 +50,16 @@ public interface RepositoryFilePersistence extends JpaRepository<RepositoryFile,
List<RepositoryFile> findByPath(String repositoryPath);
/**
* List distinct paths.
* List repository files at specified paths
*
* @param path
* @return
*/
@Query("select rf from RepositoryFile rf where rf.path in ?1")
List<RepositoryFile> findByPath(Collection<String> paths);
/**
* List distinct paths starting with the prefix
*
* @param prefix the prefix
* @param pageable the pagination
......@@ -59,12 +69,12 @@ public interface RepositoryFilePersistence extends JpaRepository<RepositoryFile,
List<String> listDistinctPaths(String prefix, Pageable pageable);
/**
* List all distinct paths (FTP)
* List distinct paths starting with the prefix
*
* @param prefix the prefix
* @return the list of paths
*/
@Query("select distinct(rf.path) from RepositoryFile rf where rf.path like ?1% and locate('/', trim(both '/' from substring(rf.path, length(?1), 200))) = 0")
@Query("select distinct(rf.path) from RepositoryFile rf where rf.path like ?1%")
List<String> listDistinctPaths(String prefix);
/**
......@@ -80,4 +90,13 @@ public interface RepositoryFilePersistence extends JpaRepository<RepositoryFile,
*/
RepositoryFile findByPathAndOriginalFilename(String path, String originalFilename);
/**
* Count files with matching path
*
* @param path Repository path
* @return Number of files at specified path
*/
@Query("select count(rf) from RepositoryFile rf where rf.path = ?1")
int countByPath(String path);
}
......@@ -17,6 +17,7 @@
package org.genesys.filerepository.service;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
......@@ -47,8 +48,8 @@ public interface RepositoryService {
* @throws InvalidRepositoryFileDataException the invalid repository file data exception
* @throws IOException when things go wrong on bytes storage level
*/
RepositoryFile addFile(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryFile metaData)
throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException;
RepositoryFile addFile(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryFile metaData) throws InvalidRepositoryPathException,
InvalidRepositoryFileDataException, IOException;
/**
* Add a new image to the file repository.
......@@ -63,8 +64,8 @@ public interface RepositoryService {
* @throws InvalidRepositoryFileDataException the invalid repository file data exception
* @throws IOException when things go wrong on bytes storage level
*/
RepositoryImage addImage(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryImage metaData)
throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException;
RepositoryImage addImage(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryImage metaData) throws InvalidRepositoryPathException,
InvalidRepositoryFileDataException, IOException;
/**
* Get repository file by its UUID.
......@@ -167,6 +168,17 @@ public interface RepositoryService {
*/
RepositoryFile moveFile(RepositoryFile repositoryFile, String newPath) throws NoSuchRepositoryFileException, InvalidRepositoryPathException;
/**
* Update path and originalFilename of repository file
*
* @param repositoryFile
* @param fullPath
* @return
* @throws InvalidRepositoryPathException
* @throws InvalidRepositoryFileDataException
*/
RepositoryFile moveAndRenameFile(RepositoryFile repositoryFile, String fullPath) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException;
/**
* List all {@link RepositoryImage} entries at specified path.
*
......@@ -176,21 +188,23 @@ public interface RepositoryService {
List<RepositoryImage> listImages(String path);
/**
* List distinct paths within the prefix.
* List all paths within the prefix, including prefix itself if such paths exist
*
* @param prefix The prefix
* @param pageRequest Pagination
* @return Distinct list of paths in repository starting with prefix
* @throws InvalidRepositoryPathException
*/
List<String> listPaths(String prefix, Pageable pageRequest);
List<String> listPaths(String prefix, Pageable pageRequest) throws InvalidRepositoryPathException;
/**
* List all paths
* List all paths within the prefix, including prefix itself if such paths exist
*
* @param prefix
* @return
* @throws InvalidRepositoryPathException
*/
List<String> listPaths(String prefix);
List<String> listPaths(String prefix) throws InvalidRepositoryPathException;
/**
* Update image metadata. The update is based on the record UUID.
......@@ -212,4 +226,22 @@ public interface RepositoryService {
*/
RepositoryImage removeImage(RepositoryImage repositoryImage) throws NoSuchRepositoryFileException, IOException;
/**
* Test if repository contains the path
*
* @param path
* @return true if at least one entry exists at specified path
* @throws InvalidRepositoryPathException
*/
boolean hasPath(Path path) throws InvalidRepositoryPathException;
/**
* Move all repository items to a new path
*
* @param currentPath
* @param newPath
* @throws InvalidRepositoryPathException
*/
void renamePath(String currentPath, String newPath) throws InvalidRepositoryPathException;
}
......@@ -33,7 +33,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// TODO: Auto-generated Javadoc
/**
* Handles magical updates to {@link ImageGallery} when files are added to {@link RepositoryImagePersistence}.
*
......@@ -61,9 +60,7 @@ public class ImageGalleryAspects {
@AfterReturning(value = "execution(* org.genesys.filerepository.persistence.RepositoryImagePersistence.save(*))", returning = "repositoryImages")
public Object afterRepositoryImageSaveIterable(final JoinPoint joinPoint, final Iterable<RepositoryImage> repositoryImages) throws Throwable {
if (LOG.isDebugEnabled()) {
LOG.debug("Many images were saved: " + repositoryImages);
}
LOG.debug("Many images were saved: {}", repositoryImages);
if (repositoryImages != null) {
repositoryImages.forEach(ri -> addImageToGallery(ri));
......@@ -84,7 +81,7 @@ public class ImageGalleryAspects {
public Object afterRepositoryImageSave(final JoinPoint joinPoint, final RepositoryImage repositoryImage) throws Throwable {
if (LOG.isTraceEnabled()) {
LOG.trace("1 image was saved: " + repositoryImage);
LOG.trace("1 image was saved: {}", repositoryImage);
}
if (repositoryImage != null) {
......@@ -105,7 +102,7 @@ public class ImageGalleryAspects {
@Around(value = "execution(* org.genesys.filerepository.persistence.RepositoryImagePersistence.delete(*)) && args(repositoryImage)")
public Object aroundRepositoryImageDelete(final ProceedingJoinPoint joinPoint, final RepositoryImage repositoryImage) throws Throwable {
if (LOG.isTraceEnabled() && repositoryImage != null) {
if (repositoryImage != null) {
LOG.trace("1 image is being deleted path={} originalFilename={}", repositoryImage.getPath(), repositoryImage.getOriginalFilename());
}
......@@ -115,9 +112,7 @@ public class ImageGalleryAspects {
joinPoint.proceed();
} catch (final Throwable e) {
if (LOG.isInfoEnabled()) {
LOG.info("Putting image back to gallery.");
}
LOG.info("Putting image back to gallery.");
addImageToGallery(repositoryImage);
throw e;
}
......@@ -139,7 +134,7 @@ public class ImageGalleryAspects {
if (imageGallery == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("No gallery at path=" + repositoryImage.getPath());
LOG.debug("No gallery at path={}", repositoryImage.getPath());
}
return;
}
......
......@@ -65,7 +65,7 @@ public class ClamAVScanner implements VirusScanner, InitializingBean {
LOG.info("Connecting to clamd at {}:{}", clamAvHost, clamAvPort);
ClamavClient c = new ClamavClient(clamAvHost, clamAvPort, Platform.UNIX);
c.ping();
LOG.info("Connected to cland at {}:{} version={}", clamAvHost, clamAvPort, c.version());
LOG.info("Connected to clamd at {}:{} version={}", clamAvHost, clamAvPort, c.version());
synchronized (this) {
this.client = c;
}
......
......@@ -151,6 +151,7 @@ public class FilesystemStorageServiceImpl implements BytesStorageService {
*/
@Override
public byte[] get(final String path, final String filename) throws IOException {
LOG.trace("Retrieveing bytes of {} {}", path, filename);
final File destinationDir = new File(repoDir, path);
final File destinationFile = new File(destinationDir, filename);
......@@ -163,6 +164,8 @@ public class FilesystemStorageServiceImpl implements BytesStorageService {
try (FileInputStream inputStream = new FileInputStream(destinationFile)) {
data = IOUtils.toByteArray(inputStream);
}
} else {
LOG.warn("Repository bytes not found at {}", destinationFile.getAbsolutePath());
}
return data;
......@@ -194,8 +197,8 @@ public class FilesystemStorageServiceImpl implements BytesStorageService {
final File destinationDir = new File(repoDir, path);
if (destinationDir.exists() && destinationDir.isDirectory()) {
LOG.info("Returning empty files list for nonexistent dir=" + destinationDir.getAbsolutePath());
if (! destinationDir.exists() || ! destinationDir.isDirectory()) {
LOG.info("Returning empty files list for nonexistent dir={}", destinationDir.getAbsolutePath());
return Collections.emptyList();
}
......
/*
* Copyright 2016 Global Crop Diversity Trust, www.croptrust.org
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -17,11 +17,15 @@
package org.genesys.filerepository.service.impl;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple path validator.
*/
public class PathValidator {
private final static Logger LOG = LoggerFactory.getLogger(PathValidator.class);
/**
* Checks if path is valid.
*
......@@ -29,7 +33,7 @@ public class PathValidator {
* @return true, if path is valid
*/
protected static boolean isValidPath(final String path) {
return path != null && path.startsWith("/") && path.endsWith("/") && !path.contains("//") && !path.contains(" /") && !path.contains("/ ") && !path.contains("?") && !path.contains("&")
return path != null && path.startsWith("/") && (path.equals("/") || ! path.endsWith("/")) && !path.contains("//") && !path.contains(" /") && !path.contains("/ ") && !path.contains("?") && !path.contains("&")
&& !path.contains("\\");
}
......@@ -41,6 +45,7 @@ public class PathValidator {
*/
public static void checkValidPath(final String path) throws InvalidRepositoryPathException {
if (!isValidPath(path)) {
LOG.warn("Invalid repository path {}", path);
throw new InvalidRepositoryPathException(path);
}
}
......
......@@ -21,9 +21,12 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
......@@ -95,9 +98,8 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#addFile(java .lang.String, java.lang.String,
* java.lang.String, byte[], org.genesys.filerepository.model.RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#addFile(java .lang.String, java.lang.String, java.lang.String, byte[],
* org.genesys.filerepository.model.RepositoryFile)
*/
@Override
@Transactional
......@@ -112,6 +114,12 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
throw new InvalidRepositoryFileDataException();
}
if (contentType.startsWith("image/")) {
// Handle images differently
LOG.trace("It's an image! contentType={}", contentType);
return addImage(repositoryPath, originalFilename, contentType, bytes, copyMetaData(metaData, new RepositoryImage()));
}
RepositoryFile repositoryFile = new RepositoryFile();
if (metaData != null) {
......@@ -147,9 +155,8 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#addImage( java.lang.String, java.lang.String,
* java.lang.String, byte[], org.genesys.filerepository.model.RepositoryImage)
* @see org.genesys.filerepository.service.RepositoryService#addImage( java.lang.String, java.lang.String, java.lang.String, byte[],
* org.genesys.filerepository.model.RepositoryImage)
*/
@Override
@Transactional
......@@ -215,7 +222,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFile(java .util.UUID)
*/
@Override
......@@ -244,7 +250,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFileBytes (java.lang.String, java.lang.String)
*/
@Override
......@@ -267,7 +272,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFiles( java.lang.String)
*/
@Override
......@@ -280,9 +284,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateFile( org.genesys.filerepository.model
* .RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#updateFile( org.genesys.filerepository.model .RepositoryFile)
*/
@Override
@Transactional
......@@ -303,9 +305,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateMetadata(org.genesys.filerepository.
* model.RepositoryImage)
* @see org.genesys.filerepository.service.RepositoryService#updateMetadata(org.genesys.filerepository. model.RepositoryImage)
*/
@Override
@Transactional
......@@ -323,9 +323,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model
* .RepositoryFile, java.lang.String, byte[])
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model .RepositoryFile, java.lang.String, byte[])
*/
@Override
@Transactional
......@@ -354,9 +352,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model
* .RepositoryImage, java.lang.String, byte[])
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model .RepositoryImage, java.lang.String, byte[])
*/
@Override
@Transactional
......@@ -394,9 +390,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#removeFile( org.genesys.filerepository.model
* .RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#removeFile( org.genesys.filerepository.model .RepositoryFile)
*/
@Override
@Transactional
......@@ -428,9 +422,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#moveFile(org .genesys2.server.filerepository.model
* .RepositoryFile, java.lang.String)
* @see org.genesys.filerepository.service.RepositoryService#moveFile(org .genesys2.server.filerepository.model .RepositoryFile, java.lang.String)
*/
@Override
@Transactional
......@@ -454,33 +446,64 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
return file;
}
@Override
@Transactional
public RepositoryFile moveAndRenameFile(final RepositoryFile repositoryFile, final String fullPath) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException {
if (fullPath == null) {
throw new NullPointerException("Full path cannot be null");
}
LOG.info("Moving path={} name={} to {}", repositoryFile.getPath(), repositoryFile.getFilename(), fullPath);
Path path = Paths.get(fullPath).normalize().toAbsolutePath();
PathValidator.checkValidPath(path.getParent().toString());
if (path.getFileName().toString().trim().length() == 0) {
throw new InvalidRepositoryFileDataException("File name cannot be blank");
}
repositoryFile.setPath(path.getParent().toString());
repositoryFile.setOriginalFilename(path.getFileName().toString());
if (repositoryFile instanceof RepositoryImage) {
return repositoryImagePersistence.save((RepositoryImage) repositoryFile);
} else {
return repositoryFilePersistence.save(repositoryFile);
}
}
/**
* Copy meta data.
*
* @param sourceFile the source file
* @param resultFile the result file
* @param source the source file
* @param result the result file
* @return
*/
private void copyMetaData(final RepositoryFile sourceFile, final RepositoryFile resultFile) {
resultFile.setContentType(sourceFile.getContentType());
resultFile.setDescription(sourceFile.getDescription());
resultFile.setOriginalFilename(sourceFile.getOriginalFilename());
resultFile.setAccessRights(sourceFile.getAccessRights());
resultFile.setBibliographicCitation(sourceFile.getBibliographicCitation());
resultFile.setCreated(sourceFile.getCreated());
resultFile.setCreatedDate(sourceFile.getCreatedDate());
resultFile.setCreator(sourceFile.getCreator());
resultFile.setExtent(sourceFile.getExtent());
resultFile.setLastModifiedDate(sourceFile.getLastModifiedDate());
resultFile.setLicense(sourceFile.getLicense());
resultFile.setPath(sourceFile.getPath());
resultFile.setRightsHolder(sourceFile.getRightsHolder());
resultFile.setSubject(sourceFile.getSubject());
resultFile.setTitle(sourceFile.getTitle());
private <T extends RepositoryFile> T copyMetaData(final RepositoryFile source, final T result) {
if (source == null) {
return result;
}
result.setContentType(source.getContentType());
result.setDescription(source.getDescription());
result.setOriginalFilename(source.getOriginalFilename());
result.setAccessRights(source.getAccessRights());
result.setBibliographicCitation(source.getBibliographicCitation());
result.setCreated(source.getCreated());
result.setCreatedDate(source.getCreatedDate());
result.setCreator(source.getCreator());
result.setExtent(source.getExtent());
result.setLastModifiedDate(source.getLastModifiedDate());
result.setLicense(source.getLicense());
result.setPath(source.getPath());
result.setRightsHolder(source.getRightsHolder());
result.setSubject(source.getSubject());
result.setTitle(source.getTitle());
return result;
}
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#listImages(java.lang.String)
*/
@Override
......@@ -488,23 +511,72 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
return repositoryImagePersistence.findByPath(repositoryPath);
}
@Override
public boolean hasPath(Path path) throws InvalidRepositoryPathException {
String p = path.normalize().toAbsolutePath().toString();
PathValidator.checkValidPath(p);
LOG.trace("Is directory path={}", p);
if (repositoryFilePersistence.countByPath(p) > 0) {
// Exact match
LOG.trace("Yes, hasPath {}", p);
return true;
} else {
// Any files within the specified parent
List<String> x = repositoryFilePersistence.listDistinctPaths(p);
LOG.trace("Testing hasPath within {}: {}", p, x);
return x.size() > 0;
}
}
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#listPaths(java.lang.String)
*/
@Override
public List<String> listPaths(final String prefix, final Pageable pageable) {
final List<String> paths = repositoryFilePersistence.listDistinctPaths(prefix, pageable);
paths.remove(prefix);
public List<String> listPaths(final String prefix, final Pageable pageable) throws InvalidRepositoryPathException {
PathValidator.checkValidPath(prefix);
// note the trailing slash
final String prefixSlash = "/".equals(prefix) ? prefix : prefix + "/";
LOG.debug("Listing paths under prefix={} page={}", prefix, pageable);
List<String> paths = repositoryFilePersistence.listDistinctPaths(prefix, pageable).stream().filter(path -> {
return path.startsWith(prefixSlash) || path.equals(prefix);
}).collect(Collectors.toList());
LOG.trace("Paths under prefix={}: {}", prefix, paths);
return paths;
}