Skip to content

Commit

Permalink
Merge pull request #202 from scireum/tbi/se4561-versioned-files
Browse files Browse the repository at this point in the history
Implements versioned files for sirius biz
  • Loading branch information
andyHa authored Nov 12, 2018
2 parents bad056e + b22b8f7 commit 9501212
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/main/java/sirius/biz/storage/versions/VersionedFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package sirius.biz.storage.versions;

import sirius.biz.storage.Storage;
import sirius.biz.storage.StoredObjectRef;
import sirius.biz.tenants.SQLTenantAware;
import sirius.db.mixing.Mapping;
import sirius.db.mixing.annotations.Length;
import sirius.db.mixing.annotations.Lob;
import sirius.db.mixing.annotations.NullAllowed;
import sirius.db.mixing.annotations.Trim;
import sirius.kernel.di.std.Register;

import java.time.LocalDateTime;

/**
* Entity holding meta information about a versioned file.
*/
@Register(framework = Storage.FRAMEWORK_STORAGE)
public class VersionedFile extends SQLTenantAware {

/**
* Path of the versioned file.
* <p>
* This string is used to identify the file which is versioned.
*/
public static final Mapping UNIQUE_IDENTIFIER = Mapping.named("uniqueIdentifier");
@Length(255)
@Trim
private String uniqueIdentifier;

/**
* The comment explaining what was changed with this change.
*/
public static final Mapping COMMENT = Mapping.named("comment");
@NullAllowed
@Lob
@Trim
private String comment;

/**
* The timestamp marking the version of the file.
*/
public static final Mapping TIMESTAMP = Mapping.named("timestamp");
private LocalDateTime timestamp;

/**
* The file holding the versioned code.
*/
public static final Mapping STORED_FILE = Mapping.named("storedFile");
@NullAllowed
private final StoredObjectRef storedFile = new StoredObjectRef(VersionedFiles.VERSIONED_FILES, false);

/**
* Contains meta information about this versioned file.
* <p>
* We can store for e.g. if this file was automatically created or if this is the default version of a template.
*/
public static final Mapping ADDITIONAL_INFORMATION = Mapping.named("additionalInformation");
@Length(255)
@NullAllowed
@Trim
private String additionalInformation;

public String getUniqueIdentifier() {
return uniqueIdentifier;
}

public void setUniqueIdentifier(String uniqueIdentifier) {
this.uniqueIdentifier = uniqueIdentifier;
}

public String getComment() {
return comment;
}

public void setComment(String comment) {
this.comment = comment;
}

public StoredObjectRef getStoredFile() {
return storedFile;
}

public LocalDateTime getTimestamp() {
return timestamp;
}

public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}

public String getAdditionalInformation() {
return additionalInformation;
}

public void setAdditionalInformation(String additionalInformation) {
this.additionalInformation = additionalInformation;
}
}
202 changes: 202 additions & 0 deletions src/main/java/sirius/biz/storage/versions/VersionedFiles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package sirius.biz.storage.versions;

import sirius.biz.storage.Storage;
import sirius.biz.storage.StoredObject;
import sirius.biz.tenants.Tenant;
import sirius.db.jdbc.OMA;
import sirius.kernel.di.std.ConfigValue;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Register;
import sirius.kernel.health.Exceptions;
import sirius.kernel.nls.NLS;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
* Small helper class to access and create {@link VersionedFile versioned files}.
*/
@Register(classes = VersionedFiles.class, framework = Storage.FRAMEWORK_STORAGE)
public class VersionedFiles {

@Part
private static OMA oma;

@Part
private static Storage storage;

/**
* Name of the used bucked.
*/
public static final String VERSIONED_FILES = "versioned-files";

@ConfigValue("storage.buckets.versioned-files.maxNumberOfVersions")
private int maxNumberOfVersions = 0;

/**
* Retrieves all versions of a versioned file.
*
* @param tenant the owning tenant
* @param uniqueIdentifier the identifier of the versioned file
* @return {@link List<VersionedFile>} list of versioned files (sorted: most current first)
*/
public List<VersionedFile> getVersions(Tenant tenant, String uniqueIdentifier) {
return oma.select(VersionedFile.class)
.eq(VersionedFile.TENANT, tenant)
.eq(VersionedFile.UNIQUE_IDENTIFIER, uniqueIdentifier)
.orderDesc(VersionedFile.TIMESTAMP)
.queryList();
}

/**
* Checks if there are any versions for the given path.
*
* @param tenant the owning tenant
* @param uniqueIdentifier the identifier of the versioned file
* @return boolean <tt>true</tt> if there are any versions, <tt>false</tt> otherwise
*/
public boolean hasVersions(Tenant tenant, String uniqueIdentifier) {
return oma.select(VersionedFile.class)
.eq(VersionedFile.TENANT, tenant)
.eq(VersionedFile.UNIQUE_IDENTIFIER, uniqueIdentifier)
.exists();
}

/**
* Deletes all versions of a versioned file.
*
* @param tenant the owning tenant
* @param uniqueIdentifier the identifier of the versioned file
*/
public void deleteVersions(Tenant tenant, String uniqueIdentifier) {
oma.select(VersionedFile.class)
.eq(VersionedFile.TENANT, tenant)
.eq(VersionedFile.UNIQUE_IDENTIFIER, uniqueIdentifier)
.delete();
}

/**
* Retrieves the {@link VersionedFile} for the given id.
*
* @param tenant the owning tenant
* @param versionId the database id of the versioned file entity
* @return the found {@link VersionedFile}
*/
public VersionedFile getFile(Tenant tenant, String versionId) {
return oma.select(VersionedFile.class)
.eq(VersionedFile.ID, versionId)
.eq(VersionedFile.TENANT, tenant)
.first()
.orElseThrow(() -> Exceptions.createHandled().withNLSKey("VersionedFiles.noVersion").handle());
}

/**
* Gets the content of the {@link VersionedFile}.
*
* @param file the {@link VersionedFile} holding the content
* @return {@link List<String>} each string holding one line of the file content
*/
public List<String> getContent(VersionedFile file) {
try (InputStream data = storage.getData(file.getStoredFile().getObject())) {
return new BufferedReader(new InputStreamReader(data)).lines().collect(Collectors.toList());
} catch (IOException e) {
throw Exceptions.handle(Storage.LOG, e);
}
}

/**
* Creates a new {@link VersionedFile}.
*
* @param tenant the owning tenant
* @param uniqueIdentifier the identifier of the versioned file
* @param content the content of the current version
* @param comment the comment explaining the changes of the current version
* @return {@link VersionedFile} the versioned file
*/
public VersionedFile createVersion(Tenant tenant, String uniqueIdentifier, String content, String comment) {
VersionedFile file = new VersionedFile();
file.setComment(comment);
file.setUniqueIdentifier(uniqueIdentifier);
file.setTimestamp(LocalDateTime.now());
file.getTenant().setValue(tenant);
file.getStoredFile().setObject(generateNewFile(file, uniqueIdentifier, content));

oma.update(file);

deleteOldVersions(tenant, uniqueIdentifier);

return file;
}

/**
* Generates a new {@link StoredObject} holding the code to be versioned.
* <p>
* Will check if the path already exists to avoid overwriting existing versions.
*
* @param file the {@link VersionedFile} associated with the versioned code
* @param uniqueIdentifier the identifier of the versioned file
* @param code the code to be versioned
* @return {@link StoredObject} the generated file
*/
private StoredObject generateNewFile(VersionedFile file, String uniqueIdentifier, String code) {
String fullPath = uniqueIdentifier + file.getTimestamp();

if (storage.findByPath(file.getTenant().getValue(), VERSIONED_FILES, fullPath).isPresent()) {
throw Exceptions.createHandled()
.withNLSKey("VersionedFiles.versionExistsConflict")
.set("date", NLS.toUserString(file.getTimestamp()))
.set("path", uniqueIdentifier)
.handle();
}

StoredObject object = storage.createTemporaryObject(file.getTenant().getValue(),
VERSIONED_FILES,
file.getStoredFile().getReference(),
fullPath);

try (OutputStream out = storage.updateFile(object)) {
out.write(code.getBytes());
} catch (IOException e) {
throw Exceptions.handle(Storage.LOG, e);
}

return object;
}

/**
* Deletes old versions of a {@link VersionedFile} identified by an unique identifier.
*
* @param tenant the owning tenant
* @param uniqueIdentifier the unique identifier of a versioned file
*/
public void deleteOldVersions(Tenant tenant, String uniqueIdentifier) {
if (maxNumberOfVersions == 0) {
return;
}

AtomicInteger filesToSkip = new AtomicInteger(maxNumberOfVersions);

oma.select(VersionedFile.class)
.eq(VersionedFile.TENANT, tenant)
.eq(VersionedFile.UNIQUE_IDENTIFIER, uniqueIdentifier)
.orderDesc(VersionedFile.TIMESTAMP)
.iterateAll(file -> {
if (filesToSkip.getAndDecrement() > 0) {
return;
}

if (file.getStoredFile().isFilled()) {
storage.delete(file.getStoredFile().getObject());
}

oma.delete(file);
});
}
}
2 changes: 2 additions & 0 deletions src/main/resources/biz_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ UserAccountController.forgotPassword.reason = Die "Passwort-Vergessen" Funktion
UserAccountController.logout = Abmelden
UserAccountController.noUserFoundForEmail = Für die angegebene eMail-Adresse wurde kein Benutzer gefunden.
UserAccountController.tooManyUsersFoundForEmail = Die angegebene eMail-Adresse ist nicht eindeutig.
VersionedFiles.noVersion = Keine Version gefunden
VersionedFiles.versionExistsConflict = Die Version ${date} für den Pfad ${uniqueIdentifier} existiert bereits und kann deswegen nicht verwendet werden.
VirtualObject.download = Herunterladen
VirtualObject.fileSize = Größe
VirtualObject.physicalKey = Physikalischer Schlüssel
Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/component-biz.conf
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ storage {
deleteFilesAfterDays = 30
}

# Defines storage for versioned files
versioned-files {
canCreate = false
canEdit = false
canDelete = false

# number of versions kept from one versioned file
# setting this number to 0 will keep all versions
maxNumberOfVersions = 0
}
}

}
Expand Down
Loading

0 comments on commit 9501212

Please sign in to comment.