diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a20975fb3cea4ebe6a120188e7999e5b59ee25..b70b2c179d9c340df77b3b42a103cbfd926b601f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 by setting the server property `EXT_ENTITY_STATE=ENABLED`. See [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more information. +* New version history feature. The "H" container flag retrieves the full + version history during a transaction (e.g. during Retrievals) and constructs + a tree of successors and predecessors of the requested entity version. * New query functionality: `ANY VERSION OF` modifier. E.g. `FIND ANY VERSION OF RECORD WITH pname=val` returns all current and old versions of records where `pname=val`. For further information, examples and limitations see the wiki diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf index b4e1f93596a6170ef04ec373dc7a8d70e51fedcc..ad73cb86d259a1de8ecd836f8e72de8aa254793e 100644 --- a/conf/core/cache.ccf +++ b/conf/core/cache.ccf @@ -28,8 +28,8 @@ jcs.region.BACKEND_JobRules.cacheattributes.MaxObjects=103 jcs.region.BACKEND_SparseEntities jcs.region.BACKEND_SparseEntities.cacheattributes.MaxObjects=1002 -jcs.region.BACKEND_RetrieveFullVersionInfo -jcs.region.BACKEND_RetrieveFullVersionInfo.cacheattributes.MaxObjects=1006 +jcs.region.BACKEND_RetrieveVersionHistory +jcs.region.BACKEND_RetrieveVersionHistory.cacheattributes.MaxObjects=1006 # PAM UserSource Caching: Cached Items expire after 60 seconds if they are not requested (idle) and after 600 seconds max. # PAM_UnixUserGroups diff --git a/conf/core/server.conf b/conf/core/server.conf index 81ae7c2c43232024549c523636cfad090996d0fd..81b35a2a7498b0ad0f6feae7a20ce44dbe570865 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -68,7 +68,7 @@ MYSQL_USER_NAME=caosdb # Password for the user MYSQL_USER_PASSWORD=caosdb # Schema of mysql procedures and tables which is required by this CaosDB instance -MYSQL_SCHEMA_VERSION=v4.0.0-rc1 +MYSQL_SCHEMA_VERSION=v4.0.0-rc2 # -------------------------------------------------- diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java index 20ec5b71a7ae6f4dd908dc38145df1c41f035e8e..5bf978343d72272309d4f4ab0f039770419c87b8 100644 --- a/src/main/java/org/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java @@ -59,7 +59,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveProp import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveQueryTemplateDefinition; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveTransactionHistory; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader; @@ -116,7 +115,6 @@ import org.caosdb.server.database.backend.interfaces.RetrievePropertiesImpl; import org.caosdb.server.database.backend.interfaces.RetrieveQueryTemplateDefinitionImpl; import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; -import org.caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl; import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl; import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; @@ -177,7 +175,6 @@ public abstract class BackendTransaction implements Undoable { setImpl(RetrieveAllImpl.class, MySQLRetrieveAll.class); setImpl(RegisterSubDomainImpl.class, MySQLRegisterSubDomain.class); setImpl(RetrieveDatatypesImpl.class, MySQLRetrieveDatatypes.class); - setImpl(RetrieveTransactionHistoryImpl.class, MySQLRetrieveTransactionHistory.class); setImpl(RetrieveUserImpl.class, MySQLRetrieveUser.class); setImpl(RetrieveParentsImpl.class, MySQLRetrieveParents.class); setImpl(GetFileRecordByPathImpl.class, MySQLGetFileRecordByPath.class); diff --git a/src/main/java/org/caosdb/server/database/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/DatabaseUtils.java index d5910eb9a93003106369f87f50bec1c0013a61bf..4f6a58a4fc50fa91170fad6139384bccb413fa81 100644 --- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java +++ b/src/main/java/org/caosdb/server/database/DatabaseUtils.java @@ -216,10 +216,7 @@ public class DatabaseUtils { ret.fileSize = rs.getLong("FileSize"); ret.fileHash = bytes2UTF8(rs.getBytes("FileHash")); - ret.version = bytes2UTF8(rs.getBytes("Version")); - ret.versionSeconds = rs.getLong("VersionSeconds"); - ret.versionNanos = rs.getInt("VersionNanos"); - + ret.versionId = bytes2UTF8(rs.getBytes("Version")); return ret; } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java index 1f1f32c69a260d374ea930ba4e1be63e34cf9ca9..1b945acf566db938f12847f758636dbfddeac3d1 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java @@ -57,7 +57,7 @@ public class MySQLInsertSparseEntity extends MySQLTransaction implements InsertS try (final ResultSet rs = insertEntityStmt.executeQuery()) { if (rs.next()) { entity.id = rs.getInt("EntityID"); - entity.version = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); + entity.versionId = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); } else { throw new TransactionException("Didn't get new EntityID back."); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveTransactionHistory.java deleted file mode 100644 index 3273f49aa85776c4fd323ed4c027f4d1fcd3fbb0..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveTransactionHistory.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.ProtoTransactionLogMessage; - -public class MySQLRetrieveTransactionHistory extends MySQLTransaction - implements RetrieveTransactionHistoryImpl { - - public MySQLRetrieveTransactionHistory(final Access access) { - super(access); - } - - public static final String STMT_RETRIEVE_HISTORY = - "SELECT transaction, realm, username, seconds, nanos FROM transaction_log WHERE entity_id=? "; - - @Override - public ArrayList<ProtoTransactionLogMessage> execute(final Integer id) - throws TransactionException { - try { - final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_HISTORY); - - final ArrayList<ProtoTransactionLogMessage> ret = new ArrayList<ProtoTransactionLogMessage>(); - stmt.setInt(1, id); - final ResultSet rs = stmt.executeQuery(); - try { - while (rs.next()) { - final String transaction = bytes2UTF8(rs.getBytes("transaction")); - final String realm = bytes2UTF8(rs.getBytes("realm")); - final String username = bytes2UTF8(rs.getBytes("username")); - final Integer seconds = rs.getInt("seconds"); - final Integer nanos = rs.getInt("nanos"); - - ret.add(new ProtoTransactionLogMessage(transaction, realm, username, seconds, nanos)); - } - return ret; - } finally { - rs.close(); - } - } catch (final SQLException e) { - throw new TransactionException(e); - } catch (final ConnectionException e) { - throw new TransactionException(e); - } - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java index a3c87ae6e97d3b26a99126b61d1d29db1ef6206a..7ecfbfb7ac70a4a5ec248eb0e2be2c590f0676aa 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java @@ -61,12 +61,16 @@ public class MySQLRetrieveVersionHistory extends MySQLTransaction String parentId = DatabaseUtils.bytes2UTF8(rs.getBytes("parent")); Long childSeconds = rs.getLong("child_seconds"); Integer childNanos = rs.getInt("child_nanos"); + String childUsername = DatabaseUtils.bytes2UTF8(rs.getBytes("child_username")); + String childRealm = DatabaseUtils.bytes2UTF8(rs.getBytes("child_realm")); VersionHistoryItem v = result.get(childId); if (v == null) { v = new VersionHistoryItem(); v.id = childId; v.seconds = childSeconds; v.nanos = childNanos; + v.username = childUsername; + v.realm = childRealm; result.put(childId, v); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java index 18b59ef9121bd2f488efcb657a3bf158b6e14308..1c8039253b4c5e74a8054dfa937fec23c55440e8 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java @@ -80,7 +80,7 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS ResultSet rs = updateEntityStmt.executeQuery(); if (rs.next()) { - spe.version = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); + spe.versionId = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); } } catch (final SQLIntegrityConstraintViolationException e) { diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveTransactionHistoryImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveTransactionHistoryImpl.java deleted file mode 100644 index b02e4d9347e572941ab31e3a1a371470e1d8ee5c..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveTransactionHistoryImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.interfaces; - -import java.util.ArrayList; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.ProtoTransactionLogMessage; - -public interface RetrieveTransactionHistoryImpl extends BackendTransactionImpl { - - public ArrayList<ProtoTransactionLogMessage> execute(Integer id) throws TransactionException; -} diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertSparseEntity.java index 1800168ebd2cd6ef4180e23046f95066597086e5..22720f836e5cbd4912f3aedccd25398e80a19324 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertSparseEntity.java @@ -69,6 +69,7 @@ public class InsertSparseEntity extends BackendTransaction { public void cleanUp() {} }); this.entity.setId(e.id); - this.entity.setVersion(new Version(e.version)); + this.entity.setVersion(new Version(e.versionId)); + this.entity.getVersion().setHead(true); } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java deleted file mode 100644 index 632ce20cfe71d0e116bbe3987a60c45b11085eec..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019 IndiScale GmbH - * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.transaction; - -import java.util.ArrayList; -import org.caosdb.datetime.UTCDateTime; -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.ProtoTransactionLogMessage; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.utils.TransactionLogMessage; - -public class RetrieveTransactionHistory extends BackendTransaction { - - private final EntityInterface entity; - - public RetrieveTransactionHistory(final EntityInterface entity) { - this.entity = entity; - } - - @Override - protected void execute() { - final RetrieveTransactionHistoryImpl t = - getImplementation(RetrieveTransactionHistoryImpl.class); - process(t.execute(entity.getId())); - } - - private void process(final ArrayList<ProtoTransactionLogMessage> l) throws TransactionException { - for (final ProtoTransactionLogMessage t : l) { - final UTCDateTime dateTime = UTCDateTime.UTCSeconds(t.seconds, t.nanos); - - this.entity.addTransactionLog( - new TransactionLogMessage(t.transaction, this.entity, t.username, dateTime)); - } - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java index 6077d6491fda93b96262b48783092578af37f4f8..a5079c4e033882c4b1068615551b5132e4ee0cba 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java @@ -22,58 +22,43 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.Collection; import java.util.HashMap; -import org.caosdb.server.database.CacheableBackendTransaction; -import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.VersionHistoryItem; import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.Version; -public abstract class RetrieveVersionHistory - extends CacheableBackendTransaction<Integer, HashMap<String, VersionHistoryItem>> { - - // TODO - // private static final ICacheAccess<String, Version> cache = - // Cache.getCache("BACKEND_RetrieveVersionHistory"); - private EntityInterface entity; - private HashMap<String, VersionHistoryItem> map; - - public static void removeCached(Integer entityId) { - // TODO - } +public class RetrieveVersionHistory extends VersionTransaction { public RetrieveVersionHistory(EntityInterface e) { - super(null); // TODO caching - this.entity = e; - } - - @Override - public HashMap<String, VersionHistoryItem> executeNoCache() throws TransactionException { - RetrieveVersionHistoryImpl impl = getImplementation(RetrieveVersionHistoryImpl.class); - return impl.execute(getKey()); + super(e); } - /** After this method call, the version map is available to the object. */ @Override protected void process(HashMap<String, VersionHistoryItem> map) throws TransactionException { - this.map = map; + super.process(map); + if (!map.isEmpty()) getEntity().setVersion(getHistory()); } @Override - protected Integer getKey() { - return entity.getId(); - } - - public HashMap<String, VersionHistoryItem> getMap() { - return this.map; - } - - public EntityInterface getEntity() { - return this.entity; + protected Version getVersion(String id) { + Version v = new Version(id); + VersionHistoryItem i = getHistoryItems().get(v.getId()); + if (i != null) { + v.setDate(UTCDateTime.UTCSeconds(i.seconds, i.nanos)); + v.setUsername(i.username); + v.setRealm(i.realm); + } + return v; } - public Collection<VersionHistoryItem> getList() { - return this.map.values(); + private Version getHistory() { + Version v = getVersion(getEntity().getVersion().getId()); + v.setSuccessors(getSuccessors(v.getId(), true)); + v.setPredecessors(getPredecessors(v.getId(), true)); + v.setHead(v.getSuccessors().isEmpty()); + v.setCompleteHistory(true); + return v; } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java index 7d9fe5fd7ea42cf3d6aa29a127a7f82f7ec1764c..8d0e54c95d17d0eb83af541b9fee2fb605b17ec9 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java @@ -23,14 +23,12 @@ package org.caosdb.server.database.backend.transaction; import java.util.HashMap; -import java.util.LinkedList; -import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.VersionHistoryItem; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Version; -public class RetrieveVersionInfo extends RetrieveVersionHistory { +public class RetrieveVersionInfo extends VersionTransaction { public RetrieveVersionInfo(EntityInterface e) { super(e); @@ -39,46 +37,19 @@ public class RetrieveVersionInfo extends RetrieveVersionHistory { @Override protected void process(HashMap<String, VersionHistoryItem> map) throws TransactionException { super.process(map); // Make the map available to the object. - if (!map.isEmpty()) getVersion(); + if (!map.isEmpty()) getEntity().setVersion(getVersion()); } - public Version getVersion() { - Version v = getEntity().getVersion(); - VersionHistoryItem i = getMap().get(v.getId()); - if (i != null) v.setDate(UTCDateTime.UTCSeconds(i.seconds, i.nanos)); - - v.setPredecessors(getPredecessors(v.getId())); - v.setSuccessors(getSuccessors(v.getId())); - return v; - } - - /** Return a list of direct children. */ - private LinkedList<Version> getSuccessors(String id) { - LinkedList<Version> result = new LinkedList<>(); - - outer: - for (VersionHistoryItem i : getList()) { - if (i.parents != null) - for (String p : i.parents) { - if (id.equals(p)) { - Version successor = new Version(i.id, i.seconds, i.nanos); - result.add(successor); - continue outer; - } - } - } - return result; + @Override + protected Version getVersion(String id) { + return new Version(id); } - /** Return a list of direct parents. */ - private LinkedList<Version> getPredecessors(String id) { - LinkedList<Version> result = new LinkedList<>(); - if (getMap().containsKey(id) && getMap().get(id).parents != null) - for (String p : getMap().get(id).parents) { - VersionHistoryItem i = getMap().get(p); - Version predecessor = new Version(i.id, i.seconds, i.nanos); - result.add(predecessor); - } - return result; + public Version getVersion() { + Version v = getVersion(getEntity().getVersion().getId()); + v.setPredecessors(getPredecessors(v.getId(), false)); + v.setSuccessors(getSuccessors(v.getId(), false)); + v.setHead(v.getSuccessors().isEmpty()); + return v; } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java index 9c20cb1d2d112e8d64ce5be2cfa8a7f8da20fe10..2bf965575884f29640a80ec3d3ab284b7779c8f2 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java @@ -54,6 +54,7 @@ public class UpdateEntity extends BackendTransaction { execute(new InsertEntityProperties(e)); + VersionTransaction.removeCached(e.getId()); execute(new RetrieveVersionInfo(e)); } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateSparseEntity.java index 2385ec843a3d499d1184b58eca06f8ae05cbe4f8..8548fb0477de9d8b52b18b8132af15295dbc9353 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateSparseEntity.java @@ -52,6 +52,6 @@ public class UpdateSparseEntity extends BackendTransaction { t.execute(spe); - this.entity.setVersion(new Version(spe.version)); + this.entity.setVersion(new Version(spe.versionId)); } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/VersionTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/VersionTransaction.java new file mode 100644 index 0000000000000000000000000000000000000000..8a98564050f5fb860402aff0f55f1dd9de417a39 --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/transaction/VersionTransaction.java @@ -0,0 +1,159 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ +package org.caosdb.server.database.backend.transaction; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.jcs.access.behavior.ICacheAccess; +import org.caosdb.server.caching.Cache; +import org.caosdb.server.database.CacheableBackendTransaction; +import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; +import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.database.proto.VersionHistoryItem; +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.Version; + +/** + * Abstract base class which retrieves and caches the full, but flag version history. The + * implementations then use the flat version history to construct either single version information + * items (see {@link RetrieveVersionInfo}) or the complete history as a tree (see {@link + * RetrieveVersionHistory}) + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ +public abstract class VersionTransaction + extends CacheableBackendTransaction<Integer, HashMap<String, VersionHistoryItem>> { + + private static final ICacheAccess<Integer, HashMap<String, VersionHistoryItem>> cache = + Cache.getCache("BACKEND_RetrieveVersionHistory"); + private EntityInterface entity; + + /** A map of all history items which belong to this entity. The keys are the version ids. */ + private HashMap<String, VersionHistoryItem> historyItems; + + /** + * Invalidate a cache item. This should be called upon update of entities. + * + * @param entityId + */ + public static void removeCached(Integer entityId) { + cache.remove(entityId); + } + + public VersionTransaction(EntityInterface e) { + super(cache); + this.entity = e; + } + + @Override + public HashMap<String, VersionHistoryItem> executeNoCache() throws TransactionException { + RetrieveVersionHistoryImpl impl = getImplementation(RetrieveVersionHistoryImpl.class); + return impl.execute(getKey()); + } + + /** After this method call, the version map is available to the object. */ + @Override + protected void process(HashMap<String, VersionHistoryItem> historyItems) + throws TransactionException { + this.historyItems = historyItems; + } + + @Override + protected Integer getKey() { + return entity.getId(); + } + + public HashMap<String, VersionHistoryItem> getHistoryItems() { + return this.historyItems; + } + + public EntityInterface getEntity() { + return this.entity; + } + + /** + * Return a list of direct predecessors. The predecessors are constructed by {@link + * #getVersion(String)}. + * + * <p>If transitive is true, this function is called recursively on the predecessors as well, + * resulting in a list of trees of predecessors, with the direct predecessors at the root(s). + * + * @param versionId + * @param transitive + * @return A list of predecessors. + */ + protected List<Version> getPredecessors(String versionId, boolean transitive) { + LinkedList<Version> result = new LinkedList<>(); + if (getHistoryItems().containsKey(versionId) + && getHistoryItems().get(versionId).parents != null) + for (String p : getHistoryItems().get(versionId).parents) { + Version predecessor = getVersion(p); + if (transitive) { + predecessor.setPredecessors(getPredecessors(p, transitive)); + } + result.add(predecessor); + } + return result; + } + + /** + * To be implemented by the base class. The idea is, that the base class decides which information + * is being included into the Version instance. + * + * @param versionId - the id of the version + * @return + */ + protected abstract Version getVersion(String versionId); + + /** + * Return a list of direct successors. The successors are constructed by {@link + * #getVersion(String)}. + * + * <p>If transitive is true, this function is called recursively on the successors as well, + * resulting in a list of trees of successors, with the direct successors at the root(s). + * + * @param versionId + * @param transitive + * @return A list of successors. + */ + protected List<Version> getSuccessors(String versionId, boolean transitive) { + LinkedList<Version> result = new LinkedList<>(); + + outer: + for (VersionHistoryItem i : getHistoryItems().values()) { + if (i.parents != null) + for (String p : i.parents) { + if (versionId.equals(p)) { + Version successor = getVersion(i.id); + result.add(successor); + if (transitive) { + successor.setSuccessors(getSuccessors(i.id, transitive)); + } + continue outer; + } + } + } + return result; + } +} diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoTransactionLogMessage.java b/src/main/java/org/caosdb/server/database/proto/ProtoTransactionLogMessage.java deleted file mode 100644 index dd5cdbebcda7dd2c5c7f9ca93be69972ecef0e2c..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/proto/ProtoTransactionLogMessage.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.proto; - -import java.io.Serializable; - -public class ProtoTransactionLogMessage implements Serializable { - - public ProtoTransactionLogMessage( - final String transaction, - final String realm, - final String username, - final long seconds, - final int nanos) { - this.transaction = transaction; - this.realm = realm; - this.username = username; - this.seconds = seconds; - this.nanos = nanos; - } - - public ProtoTransactionLogMessage() {} - - private static final long serialVersionUID = -5856887517281480754L; - public String transaction = null; - public String realm; - public String username = null; - public Long seconds = null; - public Integer nanos = null; -} diff --git a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java index 3d5a29c9c4a69670d49347864b7ed885e758192f..d2292c58825ac6807e24f3ad3d086b29dd8a6e7e 100644 --- a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java +++ b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java @@ -38,9 +38,7 @@ public class SparseEntity extends VerySparseEntity { public String filePath = null; public Long fileSize = null; public Long fileChecked = null; - public String version = null; - public Long versionSeconds = null; - public Integer versionNanos = null; + public String versionId = null; @Override public String toString() { @@ -54,9 +52,7 @@ public class SparseEntity extends VerySparseEntity { .append(this.fileHash) .append(this.filePath) .append(this.fileSize) - .append(this.version) - .append(this.versionSeconds) - .append(this.versionNanos) + .append(this.versionId) .toString(); } } diff --git a/src/main/java/org/caosdb/server/database/proto/VersionHistoryItem.java b/src/main/java/org/caosdb/server/database/proto/VersionHistoryItem.java index 26109760ecde53ea20405bd02095cd951818677d..4c0a60ce57cce03cfaa7a2c4d41af7039c463838 100644 --- a/src/main/java/org/caosdb/server/database/proto/VersionHistoryItem.java +++ b/src/main/java/org/caosdb/server/database/proto/VersionHistoryItem.java @@ -1,13 +1,27 @@ package org.caosdb.server.database.proto; import java.io.Serializable; -import java.util.LinkedList; +import java.util.List; +/** + * This class is a flat, data-only representation of a single item of version information. This + * class is an intermediate representation which abstracts away the data base results and comes in a + * form which is easily cacheable. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public class VersionHistoryItem implements Serializable { - private static final long serialVersionUID = 6319362462701459355L; + private static final long serialVersionUID = 428030617967255942L; public String id = null; - public LinkedList<String> parents = null; + public List<String> parents = null; public Long seconds = null; public Integer nanos = null; + public String username = null; + public String realm = null; + + @Override + public String toString() { + return id; + } } diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 5522b9be33f2225412c0ba473d7c5c0eec2e4507..abbdaf3372a491e6b1b02bda12bbeff25f47de79 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -34,7 +34,6 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; -import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.CaosDBException; import org.caosdb.server.accessControl.Principal; import org.caosdb.server.database.proto.SparseEntity; @@ -1055,11 +1054,9 @@ public class Entity extends AbstractObservable implements EntityInterface { setId(spe.id); this.setRole(spe.role); setEntityACL(spe.acl); - UTCDateTime versionDate = null; - if (spe.versionSeconds != null) { - versionDate = UTCDateTime.UTCSeconds(spe.versionSeconds, spe.versionNanos); + if (spe.versionId != null) { + this.version = new Version(spe.versionId); } - this.version = new Version(spe.version, versionDate); if (!isNameOverride()) { setName(spe.name); diff --git a/src/main/java/org/caosdb/server/entity/Version.java b/src/main/java/org/caosdb/server/entity/Version.java index 3f9e148583686887fab39038aa427bccf1c69b25..5529c7c5f5e8c83d95ad5afae7c1ae786806df89 100644 --- a/src/main/java/org/caosdb/server/entity/Version.java +++ b/src/main/java/org/caosdb/server/entity/Version.java @@ -20,7 +20,7 @@ package org.caosdb.server.entity; -import java.util.LinkedList; +import java.util.List; import org.caosdb.datetime.UTCDateTime; /** @@ -31,25 +31,35 @@ import org.caosdb.datetime.UTCDateTime; public class Version { private String id = null; - private LinkedList<Version> predecessors = null; - private LinkedList<Version> successors = null; + private String username = null; + private String realm = null; + private List<Version> predecessors = null; + private List<Version> successors = null; private UTCDateTime date = null; + private boolean isHead = false; + private boolean isCompleteHistory = false; public Version(String id, long seconds, int nanos) { - this(id, UTCDateTime.UTCSeconds(seconds, nanos)); + this(id, UTCDateTime.UTCSeconds(seconds, nanos), null, null); } - public Version(String id, UTCDateTime date) { + public Version(String id, UTCDateTime date, String username, String realm) { this.id = id; this.date = date; + this.username = username; + this.realm = realm; } public Version(String id) { - this(id, null); + this(id, null, null, null); } public Version() {} + public Version(String id, UTCDateTime timestamp) { + this(id, timestamp, null, null); + } + public UTCDateTime getDate() { return date; } @@ -66,19 +76,55 @@ public class Version { this.id = id; } - public LinkedList<Version> getSuccessors() { + public List<Version> getSuccessors() { return successors; } - public void setSuccessors(LinkedList<Version> successors) { + public void setSuccessors(List<Version> successors) { this.successors = successors; } - public LinkedList<Version> getPredecessors() { + public List<Version> getPredecessors() { return predecessors; } - public void setPredecessors(LinkedList<Version> predecessors) { + public void setPredecessors(List<Version> predecessors) { this.predecessors = predecessors; } + + public String getRealm() { + return realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setDate(Long timestamp) { + this.date = UTCDateTime.SystemMillisToUTCDateTime(timestamp); + } + + public boolean isHead() { + return isHead; + } + + public void setHead(boolean isHead) { + this.isHead = isHead; + } + + public boolean isCompleteHistory() { + return isCompleteHistory; + } + + public void setCompleteHistory(boolean isCompleteHistory) { + this.isCompleteHistory = isCompleteHistory; + } } diff --git a/src/main/java/org/caosdb/server/entity/xml/VersionXMLSerializer.java b/src/main/java/org/caosdb/server/entity/xml/VersionXMLSerializer.java index 65ec9880dfef43a4e460bba692ece6c9f87faa7e..40d19a5f3a0ef6d9448ed8567840e610be712391 100644 --- a/src/main/java/org/caosdb/server/entity/xml/VersionXMLSerializer.java +++ b/src/main/java/org/caosdb/server/entity/xml/VersionXMLSerializer.java @@ -30,28 +30,43 @@ import org.jdom2.Element; * @author Timm Fitschen <t.fitschen@indiscale.com> */ class VersionXMLSerializer { + public Element toElement(Version version) { - Element result = new Element("Version"); - result.setAttribute("id", version.getId()); - if (version.getDate() != null) { - result.setAttribute("date", version.getDate().toDateTimeString(TimeZone.getDefault())); - } + return toElement(version, "Version"); + } + + private Element toElement(Version version, String tag) { + Element element = new Element(tag); + setAttributes(version, element); if (version.getPredecessors() != null) { for (Version p : version.getPredecessors()) { - Element predecessor = new Element("Predecessor"); - predecessor.setAttribute("id", p.getId()); - predecessor.setAttribute("date", p.getDate().toDateTimeString(TimeZone.getDefault())); - result.addContent(predecessor); + element.addContent(toElement(p, "Predecessor")); } } if (version.getSuccessors() != null) { for (Version s : version.getSuccessors()) { - Element successor = new Element("Successor"); - successor.setAttribute("id", s.getId()); - successor.setAttribute("date", s.getDate().toDateTimeString(TimeZone.getDefault())); - result.addContent(successor); + element.addContent(toElement(s, "Successor")); } } - return result; + return element; + } + + private void setAttributes(Version version, Element element) { + element.setAttribute("id", version.getId()); + if (version.getUsername() != null) { + element.setAttribute("username", version.getUsername()); + } + if (version.getRealm() != null) { + element.setAttribute("realm", version.getRealm()); + } + if (version.getDate() != null) { + element.setAttribute("date", version.getDate().toDateTimeString(TimeZone.getDefault())); + } + if (version.isHead()) { + element.setAttribute("head", "true"); + } + if (version.isCompleteHistory()) { + element.setAttribute("completeHistory", "true"); + } } } diff --git a/src/main/java/org/caosdb/server/jobs/core/History.java b/src/main/java/org/caosdb/server/jobs/core/History.java index a7c58979f49b2a7abdc313924dc8fb6b86bbffd4..5594b5afdb841436dbdd63f96b8e2c44faef51c6 100644 --- a/src/main/java/org/caosdb/server/jobs/core/History.java +++ b/src/main/java/org/caosdb/server/jobs/core/History.java @@ -23,7 +23,7 @@ package org.caosdb.server.jobs.core; import org.apache.shiro.authz.AuthorizationException; -import org.caosdb.server.database.backend.transaction.RetrieveTransactionHistory; +import org.caosdb.server.database.backend.transaction.RetrieveVersionHistory; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.jobs.FlagJob; import org.caosdb.server.jobs.JobAnnotation; @@ -32,6 +32,11 @@ import org.caosdb.server.permissions.EntityPermission; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; +/** + * Retrieves the complete version history of each entity and appends it to the entity. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ @JobAnnotation(time = JobExecutionTime.POST_TRANSACTION, flag = "H") public class History extends FlagJob { @@ -42,7 +47,7 @@ public class History extends FlagJob { if (entity.getId() != null && entity.getId() > 0) { try { entity.checkPermission(EntityPermission.RETRIEVE_HISTORY); - final RetrieveTransactionHistory t = new RetrieveTransactionHistory(entity); + final RetrieveVersionHistory t = new RetrieveVersionHistory(entity); execute(t); } catch (final AuthorizationException e) { entity.setEntityStatus(EntityStatus.UNQUALIFIED); diff --git a/src/main/java/org/caosdb/server/transaction/Insert.java b/src/main/java/org/caosdb/server/transaction/Insert.java index f60fc1286ae909eb7fd999aa3af1f8379dd331ff..d37f7ce516f36252466555ea18fe249f2730fea1 100644 --- a/src/main/java/org/caosdb/server/transaction/Insert.java +++ b/src/main/java/org/caosdb/server/transaction/Insert.java @@ -27,7 +27,6 @@ import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.transaction.InsertEntity; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.FileProperties; -import org.caosdb.server.entity.Version; import org.caosdb.server.entity.container.InsertContainer; import org.caosdb.server.entity.container.TransactionContainer; import org.caosdb.server.permissions.EntityACL; @@ -104,10 +103,6 @@ public class Insert extends WriteTransaction<InsertContainer> { public void insert(final TransactionContainer container, final Access access) throws Exception { if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) { execute(new InsertEntity(container), access); - for (EntityInterface e : container) { - // TODO move to InsertEntity transaction - e.setVersion(new Version(e.getVersion().getId(), this.getTimestamp())); - } } }