package com.constellio.model.services.contents; import java.math.BigDecimal; import java.util.*; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.joda.time.LocalDateTime; import com.constellio.data.utils.TimeProvider; import com.constellio.model.entities.CorePermissions; import com.constellio.model.entities.records.Content; import com.constellio.model.entities.records.ContentVersion; import com.constellio.model.entities.records.wrappers.User; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_CannotDeleteLastVersion; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_ContentMustBeCheckedOut; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_ContentMustNotBeCheckedOut; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_InvalidArgument; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_NoSuchVersion; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_UserHasNoDeleteVersionPermission; import com.constellio.model.services.contents.ContentImplRuntimeException.ContentImplRuntimeException_VersionMustBeHigherThanPreviousVersion; import com.constellio.model.utils.Lazy; public class ContentImpl implements Content { private String id; private ContentVersion currentVersion; private ContentVersion currentCheckedOutVersion; private LocalDateTime checkoutDateTime; private String checkoutUserId; private Lazy<List<ContentVersion>> lazyHistory; private List<ContentVersion> history; private boolean dirty; private boolean emptyVersion; public ContentImpl(String id, ContentVersion currentVersion, Lazy<List<ContentVersion>> lazyHistory, ContentVersion currentCheckedOutVersion, LocalDateTime checkoutDateTime, String checkoutUserId, boolean emptyVersion) { this.currentVersion = currentVersion; this.emptyVersion = emptyVersion; this.checkoutDateTime = checkoutDateTime; if (currentCheckedOutVersion == null && checkoutUserId != null) { this.currentCheckedOutVersion = currentVersion; } else { this.currentCheckedOutVersion = currentCheckedOutVersion; } this.checkoutUserId = checkoutUserId; this.lazyHistory = lazyHistory; this.id = id; } private ContentImpl() { } public static ContentImpl create(String id, User user, String filename, ContentVersionDataSummary newVersion, boolean major, boolean empty) { String version = major ? "1.0" : "0.1"; return createWithVersion(id, user, filename, newVersion, version, empty); } public static ContentImpl createWithVersion(String id, User user, String filename, ContentVersionDataSummary newVersion, String version, boolean empty) { version = validateVersion(version, null, false); validateArgument("id", id); validateUserArgument(user); validateFilenameArgument(filename); valdiateNewVersionArgument(newVersion); LocalDateTime now = TimeProvider.getLocalDateTime(); ContentImpl content = new ContentImpl(); String correctFilename = correctFilename(filename); content.id = id; content.history = new ArrayList<>(); content.dirty = true; content.emptyVersion = empty; content.setNewCurrentVersion(new ContentVersion(newVersion, correctFilename, version, user.getId(), now, null)); return content; } public static ContentImpl create(String id, ContentVersion currentVersion, List<ContentVersion> history) { validateArgument("id", id); ContentImpl content = new ContentImpl(); content.dirty = true; content.id = id; content.currentVersion = currentVersion; content.history = history; return content; } public static ContentImpl createSystemContent(String filename, ContentVersionDataSummary newVersion) { validateFilenameArgument(filename); String fileName = correctFilename(filename); String id = UUID.randomUUID().toString(); boolean major = true; ContentImpl content = new ContentImpl(); content.id = id; content.history = new ArrayList<>(); content.dirty = true; content.setNewCurrentVersion(new ContentVersion(newVersion, fileName, "1.0", null, TimeProvider.getLocalDateTime(), null)); return content; } private static String correctFilename(String filename) { return filename.replace(":", ""); } private static String getFirstVersion(boolean finalized) { return finalized ? "1.0" : "0.1"; } public static String getVersionAfter(String version, boolean finalized) { int dotIndex = version.indexOf("."); Integer major = Integer.valueOf(version.substring(0, dotIndex)); Integer minor = Integer.valueOf(version.substring(dotIndex + 1)); if (finalized) { major++; minor = 0; } else { minor++; } return major + "." + minor; } private static void validateUserArgument(User argumentValue) { if (argumentValue == null) { throw new ContentImplRuntimeException_InvalidArgument("user"); } } private static void valdiateNewVersionArgument(ContentVersionDataSummary newVersion) { if (newVersion == null) { throw new ContentImplRuntimeException_InvalidArgument("new version"); } if (newVersion.getHash() == null) { throw new ContentImplRuntimeException_InvalidArgument("new version"); } if (newVersion.getMimetype() == null) { throw new ContentImplRuntimeException_InvalidArgument("new version"); } } private static void validateArgument(String argumentName, String argumentValue) { if (!StringUtils.isNotBlank(argumentValue)) { throw new ContentImplRuntimeException_InvalidArgument(argumentName); } } private static String validateVersion(String version, String currentVersionLabel, boolean empty) { try { if (!empty && currentVersionLabel != null && version != null) { Integer versionCompare = versionCompare(currentVersionLabel, version); if (versionCompare >= 0) { throw new ContentImplRuntimeException_VersionMustBeHigherThanPreviousVersion(version, currentVersionLabel); } } Double.valueOf(version); if (version.indexOf(".") == -1) { version += ".0"; } return version; } catch (NumberFormatException e) { throw new ContentImplRuntimeException_InvalidArgument("version"); } } /** * http://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java * * Compares two version strings. * * Use this instead of String.compareTo() for a non-lexicographical * comparison that works for version strings. e.g. "1.10".compareTo("1.6"). * * @note It does not work if "1.10" is supposed to be equal to "1.10.0". * * @param str1 a string of ordinal numbers separated by decimal points. * @param str2 a string of ordinal numbers separated by decimal points. * @return The result is a negative integer if str1 is _numerically_ less than str2. * The result is a positive integer if str1 is _numerically_ greater than str2. * The result is zero if the strings are _numerically_ equal. */ public static Integer versionCompare(String str1, String str2) { String[] vals1 = str1.split("\\."); String[] vals2 = str2.split("\\."); int i = 0; // set index to first non-equal ordinal or length of shortest version string while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { i++; } // compare first non-equal ordinal number if (i < vals1.length && i < vals2.length) { int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); return Integer.signum(diff); } else { // the strings are equal or one string is a substring of the other // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" return Integer.signum(vals1.length - vals2.length); } } private static void validateFilenameArgument(String argumentValue) { validateArgument("filename", argumentValue); } public String getId() { return id; } public List<ContentVersion> getHistoryVersions() { ensureHistoryIsLoaded(); return Collections.unmodifiableList(history); } @Override public List<ContentVersion> getVersions() { List<ContentVersion> versions = new ArrayList<>(); versions.addAll(getHistoryVersions()); versions.add(getCurrentVersion()); return Collections.unmodifiableList(versions); } public ContentImpl checkIn() { ensureCheckedOut(); this.checkoutDateTime = null; this.checkoutUserId = null; this.dirty = true; if (emptyVersion) { currentVersion = currentCheckedOutVersion; emptyVersion = false; } else if (!currentCheckedOutVersion.getVersion().equals(currentVersion.getVersion())) { setNewCurrentVersion(currentCheckedOutVersion); } this.currentCheckedOutVersion = null; return this; } public ContentImpl cancelCheckOut() { ensureCheckedOut(); this.checkoutDateTime = null; this.checkoutUserId = null; this.dirty = true; this.currentCheckedOutVersion = null; return this; } public ContentImpl checkInWithModificationAndName(ContentVersionDataSummary newVersion, boolean finalize, String name) { ensureCheckedOut(); valdiateNewVersionArgument(newVersion); LocalDateTime now = TimeProvider.getLocalDateTime(); String correctFilename = correctFilename(name); String userId = this.getCheckoutUserId(); String nextVersion; if (emptyVersion) { nextVersion = finalize ? "1.0" : "0.1"; } else { nextVersion = getNextVersion(finalize); } setNewCurrentVersion(new ContentVersion(newVersion, correctFilename, nextVersion, userId, now, null)); if (emptyVersion) { this.history.clear(); this.emptyVersion = false; } this.checkoutDateTime = null; this.checkoutUserId = null; this.currentCheckedOutVersion = null; this.dirty = true; return this; } public ContentImpl checkInWithModification(ContentVersionDataSummary newVersion, boolean finalize) { ensureCheckedOut(); return checkInWithModificationAndName(newVersion, finalize, currentCheckedOutVersion.getFilename()); } public ContentImpl checkOut(User user) { ensureNotCheckedOut(); this.checkoutDateTime = TimeProvider.getLocalDateTime(); this.checkoutUserId = user.getId(); this.currentCheckedOutVersion = currentVersion; this.dirty = true; return this; } // public ContentImpl setVersionHashAndMimeType(String hash, String mimetype, long length) { // // validateArgument("hash", hash); // validateArgument("mimetype", mimetype); // // if (currentCheckedOutVersion == null) { // // setNewCurrentVersion(new ContentVersion(hash, nextContentVersion.filename, mimetype, getNextVersion(), // nextContentVersion.lastModifiedBy, nextContentVersion.lastModificationDate, length)); // // } else { // String version = currentCheckedOutVersion.getVersion(); // if (version.endsWith(currentVersion.getVersion())) { // version = getNextVersion(); // } // currentCheckedOutVersion = new ContentVersion(hash, nextContentVersion.filename, mimetype, version, // nextContentVersion.lastModifiedBy, nextContentVersion.lastModificationDate, length); // } // // this.nextContentVersion = null; // this.dirty = true; // return this; // } public ContentImpl renameCurrentVersion(String newFilename) { validateFilenameArgument(newFilename); String correctedFilename = correctFilename(newFilename); if (currentCheckedOutVersion != null) { this.currentCheckedOutVersion = this.currentCheckedOutVersion.withFilename(correctedFilename); } else { this.currentVersion = this.currentVersion.withFilename(correctedFilename); } this.dirty = true; return this; } @Override public Content setVersionComment(String comment) { if (currentCheckedOutVersion != null) { this.currentCheckedOutVersion = this.currentCheckedOutVersion.withComment(comment); } else { this.currentVersion = this.currentVersion.withComment(comment); } this.dirty = true; return this; } @Override public Content setVersionModificationDatetime(LocalDateTime modificationDatetime) { if (currentCheckedOutVersion != null) { this.currentCheckedOutVersion = this.currentCheckedOutVersion.withModificationDatetime(modificationDatetime); } else { this.currentVersion = this.currentVersion.withModificationDatetime(modificationDatetime); } this.dirty = true; return this; } public ContentVersion getCurrentVersion() { return currentVersion; } @Override public ContentVersion getCurrentCheckedOutVersion() { return currentCheckedOutVersion; } public ContentVersion getCurrentVersionSeenBy(User user) { if (user.getId().equals(checkoutUserId)) { return currentCheckedOutVersion; } else { return currentVersion; } } @Override public ContentVersion getLastMajorContentVersion() { if (currentVersion.isMajor()) { return currentVersion; } else { List<ContentVersion> historyVersions = getHistoryVersions(); for (int i = historyVersions.size() - 1; i >= 0; i--) { ContentVersion historyVersion = historyVersions.get(i); if (historyVersion.isMajor()) { return historyVersion; } } } return null; } public Content updateContentWithName(User user, ContentVersionDataSummary newVersion, boolean finalize, String name) { String version; if (emptyVersion) { version = finalize ? "1.0" : "0.1"; } else { version = getNextVersion(finalize); } return updateContentWithVersionAndName(user, newVersion, version, name); } @Override public Content updateContentWithVersionAndName(User user, ContentVersionDataSummary newVersion, String version, String name) { ensureNotCheckedOut(); version = validateVersion(version, currentVersion.getVersion(), isEmptyVersion()); validateUserArgument(user); valdiateNewVersionArgument(newVersion); LocalDateTime now = TimeProvider.getLocalDateTime(); String correctedFilename = correctFilename(name); if (emptyVersion) { emptyVersion = false; currentVersion = new ContentVersion(newVersion, correctedFilename, version, user.getId(), now, null); } else { setNewCurrentVersion(new ContentVersion(newVersion, correctedFilename, version, user.getId(), now, null)); } this.dirty = true; return this; } public Content updateContent(User user, ContentVersionDataSummary newVersion, boolean finalize) { return updateContentWithName(user, newVersion, finalize, getCurrentVersion().getFilename()); } private String getNextVersion(boolean finalized) { if (currentVersion == null) { return getFirstVersion(finalized); } else { return getVersionAfter(currentVersion.getVersion(), finalized); } } public LocalDateTime getCheckoutDateTime() { return checkoutDateTime; } public String getCheckoutUserId() { return checkoutUserId; } @Override public boolean isDirty() { return dirty; } public ContentImpl updateCheckedOutContentWithName(ContentVersionDataSummary newVersion, String name) { ensureCheckedOut(); valdiateNewVersionArgument(newVersion); String correctedFilename = correctFilename(name); String version = currentCheckedOutVersion.getVersion(); if (!emptyVersion && version.equals(getCurrentVersion().getVersion())) { version = getNextVersion(false); } LocalDateTime now = TimeProvider.getLocalDateTime(); String userId = checkoutUserId; this.currentCheckedOutVersion = new ContentVersion(newVersion, correctedFilename, version, userId, now, null); this.dirty = true; return this; } public ContentImpl updateCheckedOutContent(ContentVersionDataSummary newVersion) { return updateCheckedOutContentWithName(newVersion, getCurrentCheckedOutVersion().getFilename()); } public ContentImpl finalizeVersion() { this.dirty = true; if (currentCheckedOutVersion != null) { this.checkoutDateTime = null; this.checkoutUserId = null; if (!currentCheckedOutVersion.getVersion().equals(currentVersion.getVersion())) { String version = getVersionAfter(currentCheckedOutVersion.getVersion(), true); setNewCurrentVersion(currentCheckedOutVersion.withVersion(version)); } this.currentCheckedOutVersion = null; } else { String finalizedVersionLabel = getVersionAfter(getCurrentVersion().getVersion(), true); ensureHistoryIsLoaded(); currentVersion = getCurrentVersion().withVersion(finalizedVersionLabel); } return this; } @Override public ContentVersion getVersion(String version) { if (getCurrentVersion() != null && version.equals(getCurrentVersion().getVersion())) { return getCurrentVersion(); } else if (getCurrentCheckedOutVersion() != null && version.equals(getCurrentCheckedOutVersion().getVersion())) { return getCurrentVersion(); } ensureHistoryIsLoaded(); for (ContentVersion historyVersion : history) { if (historyVersion.getVersion().equals(version)) { return historyVersion; } } throw new ContentImplRuntimeException_NoSuchVersion(version); } @Override public Content deleteVersion(String versionLabel, User user) { if (!user.has(CorePermissions.DELETE_CONTENT_VERSION).globally()) { throw new ContentImplRuntimeException_UserHasNoDeleteVersionPermission(user); } return deleteVersion(versionLabel); } @Override public Content deleteVersion(String versionLabel) { ensureHistoryIsLoaded(); if (history.isEmpty()) { throw new ContentImplRuntimeException_CannotDeleteLastVersion(); } if (versionLabel.equals(currentVersion.getVersion())) { dirty = true; this.currentVersion = history.get(history.size() - 1); history.remove(history.size() - 1); } else { for (Iterator<ContentVersion> iterator = history.iterator(); iterator.hasNext(); ) { ContentVersion version = iterator.next(); if (versionLabel.equals(version.getVersion())) { dirty = true; iterator.remove(); } } } return this; } @Override public boolean isEmptyVersion() { return emptyVersion; } private void setNewCurrentVersion(ContentVersion version) { ensureHistoryIsLoaded(); if (currentVersion != null) { history.add(currentVersion); } currentVersion = version; } private void ensureHistoryIsLoaded() { if (history == null) { history = new ArrayList<>(lazyHistory.get()); } } private void ensureCheckedOut() { if (currentCheckedOutVersion == null) { throw new ContentImplRuntimeException_ContentMustBeCheckedOut(id); } } private void ensureNotCheckedOut() { if (currentCheckedOutVersion != null) { throw new ContentImplRuntimeException_ContentMustNotBeCheckedOut(id); } } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } @Override public boolean isDeleteContentVersionPossible(String version) { ContentVersion currentVersion = getCurrentVersion(); return currentVersion != null && !currentVersion.getVersion().equals(version); } @Override public Set<String> getHashOfAllVersions() { Set<String> hashes = new HashSet<>(); hashes.add(currentVersion.getHash()); if (currentCheckedOutVersion != null) { hashes.add(currentCheckedOutVersion.getHash()); } for (ContentVersion version : getHistoryVersions()) { hashes.add(version.getHash()); } return hashes; } }