-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #202 from scireum/tbi/se4561-versioned-files
Implements versioned files for sirius biz
- Loading branch information
Showing
6 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
src/main/java/sirius/biz/storage/versions/VersionedFile.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
202
src/main/java/sirius/biz/storage/versions/VersionedFiles.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.