Commit b37e4523 authored by Matija Obreza's avatar Matija Obreza

Merge branch '23-repository-paths' into 'master'

Resolve "Repository Paths"

Closes #23

See merge request !22
parents 7deb9686 7a2277de
...@@ -172,12 +172,6 @@ ...@@ -172,12 +172,6 @@
<artifactId>application-blocks-security</artifactId> <artifactId>application-blocks-security</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.5-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.tika</groupId> <groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <artifactId>tika-core</artifactId>
......
/*
* 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;
/**
* The InvalidRepositoryPathException is thrown when Repository is not happy
* with your selected path.
*/
public class FolderNotEmptyException extends FileRepositoryException {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/**
* Instantiates a new invalid repository path exception.
*
* @param message the message
*/
public FolderNotEmptyException(final String message) {
super(message);
}
}
/*
* 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.migration;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.genesys.filerepository.model.QRepositoryFile;
import org.genesys.filerepository.model.RepositoryFile;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys.filerepository.persistence.RepositoryFilePersistence;
import org.genesys.filerepository.service.RepositoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.querydsl.jpa.impl.JPAQueryFactory;
/**
* Creates {@link RepositoryFolder} entries and their parents - Links all
* {@link RepositoryFile} to their parent folders
*
* @author Matija Obreza
*/
@Component
public class RepositoryUpgrade20180920 {
private static final Logger LOG = LoggerFactory.getLogger(RepositoryUpgrade20180920.class);
/** The tx manager. */
@Autowired
@Qualifier("transactionManager")
protected PlatformTransactionManager txManager;
@Autowired
private RepositoryFilePersistence fileRepository;
@Autowired
private RepositoryService repositoryService;
@Autowired
private JPAQueryFactory jpaQueryFactory;
public void doUpgrade() throws Exception {
TransactionTemplate tmpl = new TransactionTemplate(txManager);
tmpl.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
makeFolders();
} catch (Throwable e) {
LOG.error("Could not migrate 1.0 to 1.1 model: {}", e.getMessage(), e);
}
}
});
tmpl.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
moveFilesToFolders();
} catch (Throwable e) {
LOG.error("Could not migrate 1.0 to 1.1 model: {}", e.getMessage(), e);
}
}
});
}
/**
* Do the migration.
*
* @throws Exception the exception
*/
void makeFolders() throws Exception {
long count = 0;
// get distinct paths
List<String> paths = jpaQueryFactory.selectFrom(QRepositoryFile.repositoryFile).select(QRepositoryFile.repositoryFile.path).distinct().orderBy(
QRepositoryFile.repositoryFile.path.asc()).fetch();
for (String folderName : paths) {
final Path folderPath = Paths.get(folderName);
RepositoryFolder folder = repositoryService.getFolder(folderPath);
if (folder == null) {
LOG.warn("Creating repository path={}", folderPath);
folder = repositoryService.ensureFolder(folderPath);
LOG.warn("Created folder={}", folder.getFolderPath());
count++;
}
}
if (count == 0) {
LOG.warn("\n\n\t** All repository folders have been created **\n\t You can remove the {} bean.\n\n", this.getClass().getName());
}
}
/**
* Do the migration.
*
* @throws Exception the exception
*/
void moveFilesToFolders() throws Exception {
long count = 0;
for (RepositoryFile repoFile : fileRepository.findAll()) {
if (repoFile.getFolder() == null) {
RepositoryFolder repoFolder = repositoryService.getFolder(Paths.get(repoFile.getPath()));
if (repoFile.getFolder() == null || repoFile.getFolder().getId() != repoFolder.getId()) {
LOG.warn("Upgrading {} to folder={}", repoFile.getPath(), repoFolder.getPath());
repoFile.setFolder(repoFolder);
fileRepository.save(repoFile);
count++;
}
}
}
if (count == 0) {
LOG.warn("\n\n\t** All repository files have been moved to proper folders **\n\t You can remove the {} bean.\n\n", this.getClass().getName());
}
}
}
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.genesys.filerepository.model; package org.genesys.filerepository.model;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -28,6 +30,7 @@ import javax.persistence.Lob; ...@@ -28,6 +30,7 @@ import javax.persistence.Lob;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.OrderColumn; import javax.persistence.OrderColumn;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import org.genesys.blocks.model.AuditedVersionedModel; import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable; import org.genesys.blocks.model.Copyable;
...@@ -78,6 +81,9 @@ public class ImageGallery extends AuditedVersionedModel implements AclAwareModel ...@@ -78,6 +81,9 @@ public class ImageGallery extends AuditedVersionedModel implements AclAwareModel
@OrderColumn(name = "position") @OrderColumn(name = "position")
private List<RepositoryImage> images; private List<RepositoryImage> images;
@Transient
private Path folderPath;
/** /**
* Gets the path. * Gets the path.
* *
...@@ -175,4 +181,18 @@ public class ImageGallery extends AuditedVersionedModel implements AclAwareModel ...@@ -175,4 +181,18 @@ public class ImageGallery extends AuditedVersionedModel implements AclAwareModel
copy.images = new ArrayList<>(this.images); copy.images = new ArrayList<>(this.images);
return copy; return copy;
} }
/**
* Gets the folder path.
*
* @return the folder path
*/
public Path getFolderPath() {
synchronized (this) {
if (folderPath == null) {
this.folderPath = Paths.get(this.path);
}
}
return this.folderPath;
}
} }
...@@ -20,6 +20,7 @@ import javax.persistence.Column; ...@@ -20,6 +20,7 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Lob; import javax.persistence.Lob;
import javax.persistence.PrePersist; import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table; import javax.persistence.Table;
import org.genesys.filerepository.metadata.DocumentMetadata; import org.genesys.filerepository.metadata.DocumentMetadata;
...@@ -53,6 +54,7 @@ public class RepositoryDocument extends RepositoryFile implements DocumentMetada ...@@ -53,6 +54,7 @@ public class RepositoryDocument extends RepositoryFile implements DocumentMetada
*/ */
@Override @Override
@PrePersist @PrePersist
@PreUpdate
protected void prePersist() { protected void prePersist() {
// Don't forget the superclass! // Don't forget the superclass!
super.prePersist(); super.prePersist();
......
...@@ -16,11 +16,14 @@ ...@@ -16,11 +16,14 @@
package org.genesys.filerepository.model; package org.genesys.filerepository.model;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
...@@ -28,33 +31,41 @@ import javax.persistence.Index; ...@@ -28,33 +31,41 @@ import javax.persistence.Index;
import javax.persistence.Inheritance; import javax.persistence.Inheritance;
import javax.persistence.InheritanceType; import javax.persistence.InheritanceType;
import javax.persistence.Lob; import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist; import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.AuditedVersionedModelWithoutId; import org.genesys.blocks.model.AuditedVersionedModelWithoutId;
import org.genesys.blocks.model.Copyable; import org.genesys.blocks.model.Copyable;
import org.genesys.blocks.model.InMemoryIdGenerator; import org.genesys.blocks.model.InMemoryIdGenerator;
import org.genesys.blocks.model.SelfCleaning;
import org.genesys.blocks.security.model.AclAwareModel; import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.filerepository.metadata.BaseMetadata; import org.genesys.filerepository.metadata.BaseMetadata;
import org.genesys.filerepository.service.BytesStorageService; import org.genesys.filerepository.service.BytesStorageService;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
/** /**
* The RepositoryFile. * The RepositoryFile.
*/ */
@Entity @Entity
@Table(name = "repository_file", @Table(name = "repository_file",
// indexes // indexes
indexes = { @Index(unique = false, columnList = "path", name = "IX_repoFile_path") } indexes = { @Index(unique = false, columnList = "folder_id", name = "IX_repoFile_path") }
// unique // unique
, uniqueConstraints = { @UniqueConstraint(columnNames = { "path", "originalFilename" }) }) , uniqueConstraints = { @UniqueConstraint(columnNames = { "folder_id", "originalFilename" }) })
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class RepositoryFile extends AuditedVersionedModelWithoutId implements AclAwareModel, BaseMetadata, Copyable<RepositoryFile> { public class RepositoryFile extends AuditedVersionedModelWithoutId implements AclAwareModel, BaseMetadata, Copyable<RepositoryFile>, SelfCleaning {
/** The Constant serialVersionUID. */ /** The Constant serialVersionUID. */
private static final long serialVersionUID = -4816923593950502695L; private static final long serialVersionUID = -4816923593950502695L;
...@@ -70,11 +81,21 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -70,11 +81,21 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
@Type(type = "uuid-binary") @Type(type = "uuid-binary")
private UUID uuid; private UUID uuid;
/** The path. */ /** Path in the repository. */
// Path in the repository @Column(length = 250)
@Column(nullable = false, length = 250)
private String path; private String path;
/**
* The repository folder.
*
* @since 1.1
*/
@ManyToOne(cascade = {}, fetch = FetchType.LAZY, optional = false)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "path")
@JsonIdentityReference(alwaysAsId = true)
@JsonProperty(access = Access.READ_ONLY)
private RepositoryFolder folder;
/** The original filename as provided by the end user. */ /** The original filename as provided by the end user. */
@Column(nullable = false, length = 250) @Column(nullable = false, length = 250)
private String originalFilename; private String originalFilename;
...@@ -90,7 +111,7 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -90,7 +111,7 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
/** The subject. */ /** The subject. */
@Column @Column
@Lob @Lob
@Type(type = "org.hibernate.type.TextType") @Type(type = "org.hibernate.type.TextType")
private String subject; private String subject;
/** The description. */ /** The description. */
...@@ -158,13 +179,25 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -158,13 +179,25 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
private int size; private int size;
/** /**
* Pre persist. * Before persist and any update
*/ */
@PrePersist @PrePersist
@PreUpdate
protected void prePersist() { protected void prePersist() {
if (uuid == null) { if (uuid == null) {
uuid = UUID.randomUUID(); uuid = UUID.randomUUID();
} }
trimStringsToNull();
}
/**
* For repository files, the parent object is always a {@link RepositoryFolder}
* (can't be null).
*/
@Override
public AclAwareModel aclParentObject() {
return this.folder;
} }
/* /*
...@@ -184,7 +217,7 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -184,7 +217,7 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
* @return relative URL to the file resource * @return relative URL to the file resource
*/ */
public String getUrl() { public String getUrl() {
return getStorageFullPath(); return getStorageFullPath().toString();
} }
/** /**
...@@ -225,11 +258,11 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -225,11 +258,11 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
* *
* @return the storage path * @return the storage path
*/ */
public String getStoragePath() { public Path getStoragePath() {
if (uuid == null) { if (uuid == null) {
return null; return null;
} }
return "/" + uuid.toString().substring(0, 3); return Paths.get("/", uuid.toString().substring(0, 3));
} }
/** /**
...@@ -238,8 +271,8 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -238,8 +271,8 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
* *
* @return the storage full path * @return the storage full path
*/ */
public String getStorageFullPath() { public Path getStorageFullPath() {
return getStoragePath() + "/" + getFilename(); return getStoragePath().resolve(getFilename());
} }
/* /*
...@@ -544,24 +577,6 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -544,24 +577,6 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
this.extension = extension; this.extension = extension;
} }
/**
* Gets the path.
*
* @return the path
*/
public String getPath() {
return path;
}
/**
* Sets the path.
*
* @param path the new path
*/
public void setPath(final String path) {
this.path = path;
}
/** /**
* Gets the original url. * Gets the original url.
* *
...@@ -652,19 +667,55 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac ...@@ -652,19 +667,55 @@ public class RepositoryFile extends AuditedVersionedModelWithoutId implements Ac
this.size = size; this.size = size;
} }
/**
* Gets the folder.
*
* @return the folder
*/
public RepositoryFolder getFolder() {
return folder;
}
/**
* Sets the folder.
*
* @param folder the new folder
*/
public void setFolder(RepositoryFolder folder) {
this.folder = folder;
}
/**
* Gets the path.
*
* @return the path
*/
public String getPath() {
return path;