diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bebc70ca8ade4d56695dca9e9c47379dcd90758..f0143f3d049d95f9f66034772e847d6ecdc34155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [README.md](misc/move_files/README.md). - LDAP server may now be given and may be different from LDAP domain. See `misc/pam_authentication/ldap.conf` -- #47 - Sub-properties can now be queried, such as in `SELECT window.width FROM house`. +- #47 - Sub-properties can now be queried, such as in + `SELECT window.width FROM house`. +- Added support for versioning, if it is enabled on the backend. ### Changed diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf index 821e5d7862efb21e0aa13f8410886c6c14b10a7c..b4e1f93596a6170ef04ec373dc7a8d70e51fedcc 100644 --- a/conf/core/cache.ccf +++ b/conf/core/cache.ccf @@ -28,6 +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 # 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 d30d82b681a185a820783c6c2d8d81a62981eb64..3507886b0475341b06a8d676845e9280c67b991b 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -67,7 +67,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=v3.0.0-rc1 +MYSQL_SCHEMA_VERSION=v3.0.0-rc2 # -------------------------------------------------- @@ -181,3 +181,4 @@ CHECK_ENTITY_ACL_ROLES_MODE=MUST # part of any Entity ACL. GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml +ENTITY_VERSIONING_ENABLED=true diff --git a/pom.xml b/pom.xml index 0594c0548331f0e834fc0bc10a61fec69ffa9a69..65174bd706023526d6aba948cf890dd2e73dc3bf 100644 --- a/pom.xml +++ b/pom.xml @@ -100,7 +100,7 @@ <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> - <version>6.0.6</version> + <version>8.0.19</version> </dependency> <dependency> <groupId>org.xerial</groupId> @@ -150,7 +150,7 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-jcs-core</artifactId> - <version>2.1</version> + <version>2.2.1</version> </dependency> <dependency> <groupId>org.kohsuke</groupId> diff --git a/src/main/java/caosdb/datetime/UTCDateTime.java b/src/main/java/caosdb/datetime/UTCDateTime.java index 215de67befe403aa6a202ed178244b3de898d23e..db66ef0ad08abf0d52a8f3b1b97c6a20cc613c8a 100644 --- a/src/main/java/caosdb/datetime/UTCDateTime.java +++ b/src/main/java/caosdb/datetime/UTCDateTime.java @@ -296,7 +296,7 @@ public class UTCDateTime implements Interval { throw new NullPointerException("toString method!!!"); } - public static UTCDateTime UTCSeconds(final Long utcseconds, final Integer nanosecond) { + public static UTCDateTime UTCSeconds(final Long utcseconds, final Integer nanoseconds) { if (LEAP_SECONDS.isEmpty()) { initLeapSeconds(); } @@ -310,10 +310,10 @@ public class UTCDateTime implements Interval { if (leapSeconds2 != leapSeconds && LEAP_SECONDS.contains(systemSeconds)) { gc.add(Calendar.SECOND, -1); return new UTCDateTime( - systemSeconds, leapSeconds, nanosecond, new LeapSecondDateTimeStringStrategy(gc, 1)); + systemSeconds, leapSeconds, nanoseconds, new LeapSecondDateTimeStringStrategy(gc, 1)); } else { return new UTCDateTime( - systemSeconds, leapSeconds, nanosecond, new GregorianCalendarDateTimeStringStrategy(gc)); + systemSeconds, leapSeconds, nanoseconds, new GregorianCalendarDateTimeStringStrategy(gc)); } } diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index 45da4eb0e655f011e06deede49eec2035745fac1..cdee262f08836071d19e5a75038f4291a917b1ff 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -64,7 +64,6 @@ import caosdb.server.transaction.ChecksumUpdater; import caosdb.server.utils.FileUtils; import caosdb.server.utils.Initialization; import caosdb.server.utils.NullPrintStream; -import caosdb.server.utils.Utils; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; @@ -75,6 +74,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Properties; import java.util.TimeZone; +import java.util.UUID; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -875,7 +875,7 @@ class CaosDBComponent extends Component { public void handle(final Request request, final Response response) { long t1 = System.currentTimeMillis(); // The server request ID is just a long random number - request.getAttributes().put("SRID", Utils.getUID()); + request.getAttributes().put("SRID", UUID.randomUUID().toString()); response.setServerInfo(CaosDBServer.getServerInfo()); super.handle(request, response); log(request, response, t1); diff --git a/src/main/java/caosdb/server/caching/JCSCacheHelper.java b/src/main/java/caosdb/server/caching/JCSCacheHelper.java index 1ce455a949bcfb76a36815b3b14bcea89e676a4a..c471ed7ef860d902b56940da52a876a44fd97218 100644 --- a/src/main/java/caosdb/server/caching/JCSCacheHelper.java +++ b/src/main/java/caosdb/server/caching/JCSCacheHelper.java @@ -5,7 +5,7 @@ * 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) + * Copyright (C) 2019,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 @@ -88,6 +88,9 @@ public class JCSCacheHelper implements CacheHelper { } logger.info("Configuring JCS Caching with {}", config); } + + // If the JCS config is updated/reset, it has to be shut down before. + JCS.shutdown(); JCS.setConfigProperties(config); } diff --git a/src/main/java/caosdb/server/database/BackendTransaction.java b/src/main/java/caosdb/server/database/BackendTransaction.java index 14e65e86bd9fe05b074c731e27b601fa4ed51f68..55987005d2c8c76f432cb12065f45b598075ec9c 100644 --- a/src/main/java/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/caosdb/server/database/BackendTransaction.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2020 IndiScale GmbH + * 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 @@ -58,6 +60,7 @@ import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole; import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity; import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveTransactionHistory; import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser; +import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory; import caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader; import caosdb.server.database.backend.implementation.MySQL.MySQLSetFileCheckedTimestampImpl; import caosdb.server.database.backend.implementation.MySQL.MySQLSetPassword; @@ -113,6 +116,7 @@ import caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; import caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl; import caosdb.server.database.backend.interfaces.RetrieveUserImpl; +import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; import caosdb.server.database.backend.interfaces.RuleLoaderImpl; import caosdb.server.database.backend.interfaces.SetFileCheckedTimestampImpl; import caosdb.server.database.backend.interfaces.SetPasswordImpl; @@ -204,6 +208,7 @@ public abstract class BackendTransaction implements Undoable { setImpl( RetrieveQueryTemplateDefinitionImpl.class, MySQLRetrieveQueryTemplateDefinition.class); setImpl(InsertEntityDatatypeImpl.class, MySQLInsertEntityDatatype.class); + setImpl(RetrieveVersionHistoryImpl.class, MySQLRetrieveVersionHistory.class); } } diff --git a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java index fc4c9659d9a8ea80393bebad945bd6badf9580c4..0131b740d81cbd41d1b9263c5f42029e1af4539c 100644 --- a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java +++ b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java @@ -4,8 +4,8 @@ * * 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) + * Copyright (C) 2019,2020 IndiScale GmbH + * Copyright (C) 2019,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 @@ -51,7 +51,7 @@ public abstract class CacheableBackendTransaction<K, V extends Serializable> private final V execute(final K key) throws TransactionException { // get from cache if possible... if (cacheIsEnabled() && key != null) { - final V cached = getCache().get(getKey()); + final V cached = getCache().get(key); if (cached != null) { this.cached = true; return cached; @@ -64,7 +64,7 @@ public abstract class CacheableBackendTransaction<K, V extends Serializable> if (notCached != null) { if (cacheIsEnabled() && key != null) { // now cache if possible - getCache().put(getKey(), notCached); + getCache().put(key, notCached); } } return notCached; diff --git a/src/main/java/caosdb/server/database/DatabaseUtils.java b/src/main/java/caosdb/server/database/DatabaseUtils.java index 84c38fa448b7d8a1035aa28ded7bbfcecc5a88b3..9edc0347abf8b63de0ce76ead06fb1f6883332e1 100644 --- a/src/main/java/caosdb/server/database/DatabaseUtils.java +++ b/src/main/java/caosdb/server/database/DatabaseUtils.java @@ -173,12 +173,7 @@ public class DatabaseUtils { while (rs.next()) { final FlatProperty fp = new FlatProperty(); fp.id = rs.getInt("PropertyID"); - - final String v = bytes2UTF8(rs.getBytes("PropertyValue")); - if (v != null) { - fp.value = v; - } - + fp.value = bytes2UTF8(rs.getBytes("PropertyValue")); fp.status = bytes2UTF8(rs.getBytes("PropertyStatus")); fp.idx = rs.getInt("PropertyIndex"); ret.add(fp); @@ -217,12 +212,14 @@ public class DatabaseUtils { ret.datatype = bytes2UTF8(rs.getBytes("Datatype")); ret.collection = bytes2UTF8(rs.getBytes("Collection")); - final String path = bytes2UTF8(rs.getBytes("FilePath")); - if (!rs.wasNull()) { - ret.filePath = path; - ret.fileSize = rs.getLong("FileSize"); - ret.fileHash = bytes2UTF8(rs.getBytes("FileHash")); - } + ret.filePath = bytes2UTF8(rs.getBytes("FilePath")); + 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"); + return ret; } diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java index 9700daf3a99b30e42baf8580f863998d7efe67a1..fc17dd1066cfa57660d44dbb6a521d3177e5fc72 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java @@ -85,8 +85,7 @@ class DatabaseConnectionPool { + CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_PORT) + "/" + CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_DATABASE_NAME) - + "?noAccessToProcedureBodies=true&cacheCallableStmts=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8_unicode_ci&characterSetResults=utf8&serverTimezone=CET"; - // + "?profileSQL=true&characterSetResults=utf8"; + + "?noAccessToProcedureBodies=true&autoReconnect=true&serverTimezone=UTC&characterEncoding=UTF-8"; final String user = CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_USER_NAME); final String pwd = CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_USER_PASSWORD); final ConnectionPool pool = new ConnectionPool("MySQL Pool", 2, 5, 0, 0, url, user, pwd); diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java index 458823167423893b6defb166f013618be3a796d7..b443c54507e415ed4171fcf0ac99c3da762fb506 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -22,15 +24,20 @@ */ package caosdb.server.database.backend.implementation.MySQL; +import caosdb.server.accessControl.Principal; import caosdb.server.database.misc.DBHelper; +import caosdb.server.transaction.ChecksumUpdater; import caosdb.server.transaction.TransactionInterface; import caosdb.server.transaction.WriteTransaction; -import caosdb.server.utils.Info; -import caosdb.server.utils.Initialization; +import java.io.UnsupportedEncodingException; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.Statement; import java.util.HashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Provides cached statements for a MySQL back-end. @@ -41,23 +48,71 @@ public class MySQLHelper implements DBHelper { private Connection connection = null; + private Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Initialize a transaction by calling the corresponding SQL procedure. + * + * <p>In the database, this adds a row to the transaction table with SRID, user and timestamp. + */ + public void initTransaction(Connection connection, WriteTransaction<?> transaction) + throws SQLException { + try (CallableStatement call = connection.prepareCall("CALL set_transaction(?,?,?,?,?)")) { + + String username = ((Principal) transaction.getTransactor().getPrincipal()).getUsername(); + String realm = ((Principal) transaction.getTransactor().getPrincipal()).getRealm(); + long seconds = transaction.getTimestamp().getUTCSeconds(); + int nanos = transaction.getTimestamp().getNanoseconds(); + byte[] srid = transaction.getSRID().getBytes("UTF-8"); + + call.setBytes(1, srid); + call.setString(2, username); + call.setString(3, realm); + call.setLong(4, seconds); + call.setInt(5, nanos); + call.execute(); + + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + System.exit(1); + } + } + + public Connection initConnection(TransactionInterface transaction) + throws ConnectionException, SQLException { + Connection connection; + connection = DatabaseConnectionPool.getConnection(); + + if (transaction instanceof ChecksumUpdater) { + connection.setReadOnly(false); + connection.setAutoCommit(false); + } else if (transaction instanceof WriteTransaction) { + connection.setReadOnly(false); + connection.setAutoCommit(false); + initTransaction(connection, (WriteTransaction<?>) transaction); + } else { + connection.setReadOnly(false); + connection.setAutoCommit(true); + } + + return connection; + } + public Connection getConnection() throws SQLException, ConnectionException { if (this.connection == null) { - this.connection = DatabaseConnectionPool.getConnection(); - if (this.transaction instanceof WriteTransaction) { - this.connection.setReadOnly(false); - this.connection.setAutoCommit(false); - } else if (this.transaction instanceof Initialization || this.transaction instanceof Info) { - this.connection.setReadOnly(false); - this.connection.setAutoCommit(true); - } else { - this.connection.setReadOnly(false); - this.connection.setAutoCommit(true); - } + this.connection = initConnection(this.transaction); } return this.connection; } + /** + * Prepare a statement from a string. Reuse prepared statements from the cache if available. + * + * @param statement + * @return + * @throws SQLException + * @throws ConnectionException + */ public PreparedStatement prepareStatement(final String statement) throws SQLException, ConnectionException { if (this.stmtCache.containsKey(statement)) { @@ -81,6 +136,7 @@ public class MySQLHelper implements DBHelper { private TransactionInterface transaction = null; + /** Make all changes permanent. */ @Override public void commit() throws SQLException { if (this.connection != null @@ -90,13 +146,20 @@ public class MySQLHelper implements DBHelper { } } + /** + * Reset SRID variable, close all statements, roll back to last save point and close connection. + */ @Override public void cleanUp() { - // close all statements (if necessary), roll back to last save point (if - // possible) and close connection (if necessary). try { if (this.connection != null && !this.connection.isClosed()) { + try (Statement s = connection.createStatement()) { + s.execute("SET @SRID = NULL"); + } catch (SQLException e) { + logger.error("Exception while resetting the @SRID variable.", e); + } + // close all cached statements (if possible) for (final PreparedStatement stmt : this.stmtCache.values()) { try { @@ -104,7 +167,7 @@ public class MySQLHelper implements DBHelper { stmt.close(); } } catch (final SQLException e) { - e.printStackTrace(); + logger.warn("Exception while closing a prepared statement.", e); } } @@ -113,12 +176,12 @@ public class MySQLHelper implements DBHelper { this.connection.rollback(); } } catch (final SQLException r) { - r.printStackTrace(); + logger.warn("Exception during roll-back attempt.", r); } this.connection.close(); } } catch (final SQLException e) { - e.printStackTrace(); + logger.warn("Exception during clean-up.", e); } // clear everything diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java index 4ad9f5b6a3cc3e51c05bc83bbc9332807b79484d..97cfde68b1b9097152aca3ea68705e6cdfddfaad 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java @@ -22,6 +22,7 @@ */ package caosdb.server.database.backend.implementation.MySQL; +import caosdb.server.database.DatabaseUtils; import caosdb.server.database.access.Access; import caosdb.server.database.backend.interfaces.InsertSparseEntityImpl; import caosdb.server.database.exceptions.IntegrityException; @@ -56,6 +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")); } else { throw new TransactionException("Didn't get new EntityID back."); } diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java index c1c430cfee6e7a6b88391aa02df072e918f8d692..721ba566ed389732456342aab78691c171b33922 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java @@ -30,6 +30,7 @@ import caosdb.server.database.proto.VerySparseEntity; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; public class MySQLRetrieveParents extends MySQLTransaction implements RetrieveParentsImpl { @@ -38,16 +39,22 @@ public class MySQLRetrieveParents extends MySQLTransaction implements RetrievePa super(access); } - private static final String stmtStr = "call retrieveEntityParents(?)"; + private static final String stmtStr = "call retrieveEntityParents(?, ?)"; @Override - public ArrayList<VerySparseEntity> execute(final Integer id) throws TransactionException { + public ArrayList<VerySparseEntity> execute(final Integer id, final String version) + throws TransactionException { try { ResultSet rs = null; try { final PreparedStatement prepareStatement = prepareStatement(stmtStr); prepareStatement.setInt(1, id); + if (version == null) { + prepareStatement.setNull(2, Types.VARBINARY); + } else { + prepareStatement.setString(2, version); + } rs = prepareStatement.executeQuery(); return DatabaseUtils.parseParentResultSet(rs); } finally { diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java index 8b9a547bac69cf221dbf7c204a93e95326bb484c..9b6704c4cb3e6395c5b8239a3aefc34bb818e937 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java @@ -31,6 +31,7 @@ import caosdb.server.database.proto.ProtoProperty; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; import java.util.List; @@ -40,15 +41,17 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev super(access); } - private static final String stmtStr = "call retrieveEntityProperties(?,?)"; - private static final String stmtStr2 = "call retrieveOverrides(?,?)"; + private static final String stmtStr = "call retrieveEntityProperties(?,?,?)"; + private static final String stmtStr2 = "call retrieveOverrides(?,?,?)"; @Override - public ArrayList<ProtoProperty> execute(final Integer entity) throws TransactionException { + public ArrayList<ProtoProperty> execute(final Integer entity, final String version) + throws TransactionException { try { final PreparedStatement prepareStatement = prepareStatement(stmtStr); - final List<FlatProperty> props = retrieveFlatPropertiesStage1(0, entity, prepareStatement); + final List<FlatProperty> props = + retrieveFlatPropertiesStage1(0, entity, version, prepareStatement); final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>(); for (final FlatProperty p : props) { @@ -56,7 +59,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev proto.property = p; final List<FlatProperty> subProps = - retrieveFlatPropertiesStage1(entity, p.id, prepareStatement); + retrieveFlatPropertiesStage1(entity, p.id, version, prepareStatement); proto.subProperties = subProps; @@ -71,7 +74,10 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev } private List<FlatProperty> retrieveFlatPropertiesStage1( - final Integer domain, final Integer entity, final PreparedStatement stmt) + final Integer domain, + final Integer entity, + final String version, + final PreparedStatement stmt) throws SQLException, ConnectionException { ResultSet rs = null; try { @@ -82,6 +88,12 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev } stmt.setInt(2, entity); + if (version == null) { + stmt.setNull(3, Types.VARBINARY); + } else { + stmt.setString(3, version); + } + long t1 = System.currentTimeMillis(); rs = stmt.executeQuery(); long t2 = System.currentTimeMillis(); @@ -91,7 +103,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev final PreparedStatement stmt2 = prepareStatement(stmtStr2); - retrieveOverrides(domain, entity, stmt2, props); + retrieveOverrides(domain, entity, version, stmt2, props); return props; } finally { @@ -104,6 +116,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev private void retrieveOverrides( final Integer domain, final Integer entity, + final String version, final PreparedStatement stmt2, final List<FlatProperty> props) throws SQLException { @@ -112,6 +125,11 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev try { stmt2.setInt(1, domain); stmt2.setInt(2, entity); + if (version == null) { + stmt2.setNull(3, Types.VARBINARY); + } else { + stmt2.setString(3, version); + } long t1 = System.currentTimeMillis(); rs = stmt2.executeQuery(); long t2 = System.currentTimeMillis(); diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java index 92f2cd5fe4c26d796112baec5d908b9431187ae7..c6d88ec9f8fca1651318456161ed2c0347a47f21 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java @@ -28,6 +28,7 @@ import caosdb.server.database.exceptions.TransactionException; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; public class MySQLRetrieveQueryTemplateDefinition extends MySQLTransaction implements RetrieveQueryTemplateDefinitionImpl { @@ -37,14 +38,19 @@ public class MySQLRetrieveQueryTemplateDefinition extends MySQLTransaction } public static final String STMT_RETRIEVE_QUERY_TEMPLATE_DEF = - "SELECT definition FROM query_template_def WHERE id=?"; + "call retrieveQueryTemplateDef(?,?)"; @Override - public String retrieve(final Integer id) { + public String retrieve(final Integer id, final String version) { try { final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_QUERY_TEMPLATE_DEF); stmt.setInt(1, id); + if (version == null) { + stmt.setNull(2, Types.VARBINARY); + } else { + stmt.setString(2, version); + } final ResultSet rs = stmt.executeQuery(); if (rs.next()) { return rs.getString("definition"); diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java index 2aff2aaedda70672eb676597a2e3ba7a1db14352..097961974b0c599480e8186f170665838e7d046e 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java @@ -30,6 +30,7 @@ import caosdb.server.database.proto.SparseEntity; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; /** * Retrieve a single SparseEntity by id. @@ -43,14 +44,19 @@ public class MySQLRetrieveSparseEntity extends MySQLTransaction super(access); } - private static final String stmtStr = "call retrieveEntity(?)"; + private static final String stmtStr = "call retrieveEntity(?,?)"; @Override - public SparseEntity execute(final int id) throws TransactionException { + public SparseEntity execute(final int id, final String version) throws TransactionException { try { final PreparedStatement preparedStatement = prepareStatement(stmtStr); preparedStatement.setInt(1, id); + if (version == null) { + preparedStatement.setNull(2, Types.VARBINARY); + } else { + preparedStatement.setString(2, version); + } try (final ResultSet rs = preparedStatement.executeQuery()) { if (rs.next()) { return DatabaseUtils.parseEntityResultSet(rs); diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java new file mode 100644 index 0000000000000000000000000000000000000000..22ac0c2e30c0f48fbc884f40593bac2dc45d7b7d --- /dev/null +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java @@ -0,0 +1,85 @@ +/* + * ** 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 caosdb.server.database.backend.implementation.MySQL; + +import caosdb.server.database.DatabaseUtils; +import caosdb.server.database.access.Access; +import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; +import caosdb.server.database.exceptions.TransactionException; +import caosdb.server.database.proto.VersionHistoryItem; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedList; + +/** + * Transaction to retrieve all versions of an entity. + * + * <p>Creates a mapping ID :: (VersionHistoryItem) + */ +public class MySQLRetrieveVersionHistory extends MySQLTransaction + implements RetrieveVersionHistoryImpl { + + public static final String VERSION_HISTORY_STMT = "CALL get_version_history(?)"; + + public MySQLRetrieveVersionHistory(Access access) { + super(access); + } + + @Override + public HashMap<String, VersionHistoryItem> execute(Integer entityId) { + + HashMap<String, VersionHistoryItem> result = new HashMap<>(); + try { + PreparedStatement s = prepareStatement(VERSION_HISTORY_STMT); + s.setInt(1, entityId); + ResultSet rs = s.executeQuery(); + + while (rs.next()) { + String childId = DatabaseUtils.bytes2UTF8(rs.getBytes("child")); + String parentId = DatabaseUtils.bytes2UTF8(rs.getBytes("parent")); + Long childSeconds = rs.getLong("child_seconds"); + Integer childNanos = rs.getInt("child_nanos"); + VersionHistoryItem v = result.get(childId); + if (v == null) { + v = new VersionHistoryItem(); + v.id = childId; + v.seconds = childSeconds; + v.nanos = childNanos; + result.put(childId, v); + } + + if (parentId != null) { + if (v.parents == null) { + v.parents = new LinkedList<>(); + } + v.parents.add(parentId); + } + } + } catch (SQLException | ConnectionException e) { + throw new TransactionException(e); + } + return result; + } +} diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java index 4cc59f9d0f0a813bb92294f6fabb1cd51f43c151..73c589fade317586cc1b5bfb4b796578f9e819ba 100644 --- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java @@ -22,12 +22,14 @@ */ package caosdb.server.database.backend.implementation.MySQL; +import caosdb.server.database.DatabaseUtils; import caosdb.server.database.access.Access; import caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl; import caosdb.server.database.exceptions.IntegrityException; import caosdb.server.database.exceptions.TransactionException; import caosdb.server.database.proto.SparseEntity; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; import java.sql.Types; @@ -39,15 +41,31 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS } public static final String STMT_UPDATE_ENTITY = "call updateEntity(?,?,?,?,?,?,?)"; - public static final String STMT_UPDATE_FILE_PROPS = - "INSERT INTO files (hash, size, path, file_id) VALUES (unhex(?),?,?,?) ON DUPLICATE KEY UPDATE hash=unhex(?), size=?, path=?;"; + public static final String STMT_UPDATE_FILE_PROPS = "call setFileProperties(?,?,?,?)"; @Override public void execute(final SparseEntity spe) throws TransactionException { try { - final PreparedStatement updateEntityStmt = prepareStatement(STMT_UPDATE_ENTITY); + // file properties; + final PreparedStatement updateFilePropsStmt = prepareStatement(STMT_UPDATE_FILE_PROPS); + updateFilePropsStmt.setInt(1, spe.id); + if (spe.filePath != null) { + updateFilePropsStmt.setString(2, spe.filePath); + updateFilePropsStmt.setLong(3, spe.fileSize); + if (spe.fileHash != null) { + updateFilePropsStmt.setString(4, spe.fileHash); + } else { + updateFilePropsStmt.setNull(4, Types.VARCHAR); + } + } else { + updateFilePropsStmt.setNull(2, Types.VARCHAR); + updateFilePropsStmt.setNull(3, Types.BIGINT); + updateFilePropsStmt.setNull(4, Types.VARCHAR); + } + updateFilePropsStmt.execute(); // very sparse entity + final PreparedStatement updateEntityStmt = prepareStatement(STMT_UPDATE_ENTITY); updateEntityStmt.setInt(1, spe.id); updateEntityStmt.setString(2, spe.name); updateEntityStmt.setString(3, spe.description); @@ -59,28 +77,12 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS } updateEntityStmt.setString(6, spe.collection); updateEntityStmt.setString(7, spe.acl); - updateEntityStmt.execute(); + ResultSet rs = updateEntityStmt.executeQuery(); - // file properties; - if (spe.filePath != null) { - final PreparedStatement updateFilePropsStmt = prepareStatement(STMT_UPDATE_FILE_PROPS); - if (spe.fileHash != null) { - updateFilePropsStmt.setString(1, spe.fileHash); - updateFilePropsStmt.setString(5, spe.fileHash); - } else { - updateFilePropsStmt.setNull(1, Types.VARCHAR); - updateFilePropsStmt.setNull(5, Types.VARCHAR); - } - updateFilePropsStmt.setLong(2, spe.fileSize); - updateFilePropsStmt.setLong(6, spe.fileSize); - - updateFilePropsStmt.setString(3, spe.filePath); - updateFilePropsStmt.setString(7, spe.filePath); - - updateFilePropsStmt.setInt(4, spe.id); - - updateFilePropsStmt.execute(); + if (rs.next()) { + spe.version = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); } + } catch (final SQLIntegrityConstraintViolationException e) { throw new IntegrityException(e); } catch (final SQLException e) { diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java index 551ee962d8768aa90b9f5aaed313c050f0873079..9dfb8cf57c260c7fe154c25f095886ca6a3c2698 100644 --- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java +++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java @@ -28,5 +28,6 @@ import java.util.ArrayList; public interface RetrieveParentsImpl extends BackendTransactionImpl { - public ArrayList<VerySparseEntity> execute(Integer id) throws TransactionException; + public ArrayList<VerySparseEntity> execute(Integer id, String version) + throws TransactionException; } diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java index 3c8449f7fc62581457091ea5ab0ccc5921b89a92..b9a15a526b857b9674432a96733c6f2fd8e5d4ea 100644 --- a/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java +++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java @@ -28,5 +28,5 @@ import java.util.ArrayList; public interface RetrievePropertiesImpl extends BackendTransactionImpl { - public ArrayList<ProtoProperty> execute(Integer id) throws TransactionException; + public ArrayList<ProtoProperty> execute(Integer id, String version) throws TransactionException; } diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java index fee860ca2c0ee4341ae91a4a1736d518cd71a5e8..cda336044de4bd0d37a3d67da140c744f2699f32 100644 --- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java +++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java @@ -24,5 +24,5 @@ package caosdb.server.database.backend.interfaces; public interface RetrieveQueryTemplateDefinitionImpl extends BackendTransactionImpl { - public String retrieve(final Integer id); + public String retrieve(Integer id, String version); } diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java index 82a534c03182d577e7b27eca9b2ff9def1c60677..5b6978f8f456bf978c3ca60ae0da479eaf54ed0a 100644 --- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java +++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java @@ -27,5 +27,5 @@ import caosdb.server.database.proto.SparseEntity; public interface RetrieveSparseEntityImpl extends BackendTransactionImpl { - public SparseEntity execute(int id) throws TransactionException; + public SparseEntity execute(int id, String version) throws TransactionException; } diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..118e783d816ce45d1a43738bf8959e4a94740d3c --- /dev/null +++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java @@ -0,0 +1,9 @@ +package caosdb.server.database.backend.interfaces; + +import caosdb.server.database.proto.VersionHistoryItem; +import java.util.HashMap; + +public interface RetrieveVersionHistoryImpl extends BackendTransactionImpl { + + public HashMap<String, VersionHistoryItem> execute(Integer entityId); +} diff --git a/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java b/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java index 15ace2b9820398cb270a6a1e098e6f642b66ab9d..984ca5eb853e356814aafb69ebd9860bee4fe8a1 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java +++ b/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java @@ -40,8 +40,8 @@ public class DeleteEntityProperties extends BackendTransaction { @Override public void execute() { - RetrieveProperties.removeCached(this.entity.getId()); - RetrieveParents.removeCached(this.entity.getId()); + RetrieveProperties.removeCached(this.entity.getIdVersion()); + RetrieveParents.removeCached(this.entity.getIdVersion()); final DeleteEntityPropertiesImpl ret = getImplementation(DeleteEntityPropertiesImpl.class); diff --git a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java index 39deb1416c743f838bfc1b96c29fd845dbcef742..a2e4de6482d78053ab01c70ea3634c3f8af232a3 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java @@ -42,7 +42,7 @@ public class DeleteSparseEntity extends BackendTransaction { @Override protected void execute() { - RetrieveSparseEntity.removeCached(this.entity.getId()); + RetrieveSparseEntity.removeCached(this.entity); if (entity.hasFileProperties()) { GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath()); } diff --git a/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java index f8711197064a88dd92c408f329d1a36df55c5ed2..c8e20895711e16c40c75c89c9776e8ad55851117 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java @@ -30,6 +30,7 @@ import caosdb.server.database.exceptions.IntegrityException; import caosdb.server.database.exceptions.TransactionException; import caosdb.server.database.proto.SparseEntity; import caosdb.server.entity.EntityInterface; +import caosdb.server.entity.Version; import caosdb.server.utils.Undoable; public class InsertSparseEntity extends BackendTransaction { @@ -68,5 +69,6 @@ public class InsertSparseEntity extends BackendTransaction { public void cleanUp() {} }); this.entity.setId(e.id); + this.entity.setVersion(new Version(e.version)); } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java index a1817767650175f604286a29ea82b5387a4dc2d6..ad0d39e94b56904def08a04a8a42a89627a7b4d6 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java @@ -107,6 +107,7 @@ public class RetrieveFullEntity extends BackendTransaction { } execute(new RetrieveParents(e)); execute(new RetrieveProperties(e)); + execute(new RetrieveVersionInfo(e)); // recursion! retrieveSubEntities calls retrieveFull sometimes, but with reduced selectors. if (selections != null && !selections.isEmpty()) { @@ -143,7 +144,7 @@ public class RetrieveFullEntity extends BackendTransaction { RetrieveEntity ref = new RetrieveEntity(value.getId()); // recursion! (Only for the matching selections) retrieveFullEntity(ref, getSubSelects(selections, propertyName)); - value.setEntity(ref); + value.setEntity(ref, true); } } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java index 5c1b0178f51496f83a4fac6a4ce3b2447abdccd5..d33d5e96d978fe86d6eb965113726eb79bb60d40 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java @@ -34,10 +34,22 @@ import caosdb.server.entity.EntityInterface; import java.util.ArrayList; import org.apache.commons.jcs.access.behavior.ICacheAccess; +// TODO Problem with the caching. +// When an old entity version has a parent which is deleted, the name is +// still in the cached VerySparseEntity. This can be resolved by using a +// similar strategy as in RetrieveProperties.java where the name etc. are +// retrieved in a second step. Thus the deletion doesn't slip through +// unnoticed. +// +// Changes are necessary in the backend-api, i.e. mysqlbackend and the +// interfaces as well. +// +// See also a failing test in caosdb-pyinttest: +// tests/test_version.py::test_bug_cached_parent_name_in_old_version public class RetrieveParents - extends CacheableBackendTransaction<Integer, ArrayList<VerySparseEntity>> { + extends CacheableBackendTransaction<String, ArrayList<VerySparseEntity>> { - private static final ICacheAccess<Integer, ArrayList<VerySparseEntity>> cache = + private static final ICacheAccess<String, ArrayList<VerySparseEntity>> cache = Cache.getCache("BACKEND_EntityParents"); /** @@ -45,9 +57,9 @@ public class RetrieveParents * * @param id */ - public static void removeCached(final Integer id) { - if (id != null && cache != null) { - cache.remove(id); + public static void removeCached(final String idVersion) { + if (idVersion != null && cache != null) { + cache.remove(idVersion); } } @@ -61,18 +73,18 @@ public class RetrieveParents @Override public ArrayList<VerySparseEntity> executeNoCache() throws TransactionException { final RetrieveParentsImpl t = getImplementation(RetrieveParentsImpl.class); - final Integer key = getKey(); - return t.execute(key); + return t.execute(this.entity.getId(), this.entity.getVersion().getId()); } @Override protected void process(final ArrayList<VerySparseEntity> t) throws TransactionException { this.entity.getParents().clear(); + DatabaseUtils.parseParentsFromVerySparseEntity(this.entity, t); } @Override - protected Integer getKey() { - return this.entity.getId(); + protected String getKey() { + return this.entity.getIdVersion(); } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java index 487feb56cf848e948fcacb8f33f49e4a0bc2267d..822f57fd49e54d7517578592acf09100924fe81b 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java @@ -37,11 +37,11 @@ import java.util.ArrayList; import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveProperties - extends CacheableBackendTransaction<Integer, ArrayList<ProtoProperty>> { + extends CacheableBackendTransaction<String, ArrayList<ProtoProperty>> { private final EntityInterface entity; public static final String CACHE_REGION = "BACKEND_EntityProperties"; - private static final ICacheAccess<Integer, ArrayList<ProtoProperty>> cache = + private static final ICacheAccess<String, ArrayList<ProtoProperty>> cache = Cache.getCache(CACHE_REGION); /** @@ -49,9 +49,9 @@ public class RetrieveProperties * * @param id */ - protected static void removeCached(final Integer id) { - if (id != null && cache != null) { - cache.remove(id); + protected static void removeCached(final String idVersion) { + if (idVersion != null && cache != null) { + cache.remove(idVersion); } } @@ -63,7 +63,7 @@ public class RetrieveProperties @Override public ArrayList<ProtoProperty> executeNoCache() throws TransactionException { final RetrievePropertiesImpl t = getImplementation(RetrievePropertiesImpl.class); - return t.execute(getKey()); + return t.execute(this.entity.getId(), this.entity.getVersion().getId()); } @Override @@ -98,7 +98,7 @@ public class RetrieveProperties } @Override - protected Integer getKey() { - return this.entity.getId(); + protected String getKey() { + return this.entity.getIdVersion(); } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java index 208c7439aa6cf9ff8f32abc8f5cd59ea9a3cba95..0c96c72cb0be41aaf368c22009526914994bc8ec 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java @@ -39,6 +39,7 @@ public class RetrieveQueryTemplateDefinition extends BackendTransaction { protected void execute() throws TransactionException { final RetrieveQueryTemplateDefinitionImpl t = getImplementation(RetrieveQueryTemplateDefinitionImpl.class); - this.entity.setQueryTemplateDefinition(t.retrieve(this.entity.getId())); + this.entity.setQueryTemplateDefinition( + t.retrieve(this.entity.getId(), this.entity.getVersion().getId())); } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java index a91b5021f1fa722a8e8831234fc8ebc2032ba115..7843f57957d392c4bbf65f01a60d559cff11c6c8 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java @@ -3,9 +3,9 @@ * 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) + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019,2020 IndiScale GmbH + * Copyright (C) 2019,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 @@ -35,20 +35,21 @@ import caosdb.server.entity.EntityInterface; import caosdb.server.utils.EntityStatus; import org.apache.commons.jcs.access.behavior.ICacheAccess; -public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, SparseEntity> { +public class RetrieveSparseEntity extends CacheableBackendTransaction<String, SparseEntity> { private final EntityInterface entity; - private static final ICacheAccess<Integer, SparseEntity> cache = + private static final ICacheAccess<String, SparseEntity> cache = Cache.getCache("BACKEND_SparseEntities"); /** - * To be called by {@link UpdateSparseEntity} and {@link DeleteEntity} on execution. + * To be called by {@link UpdateSparseEntity} and {@link DeleteSparseEntity} on execution. * - * @param id + * @param entity */ - public static void removeCached(final Integer id) { - if (id != null && cache != null) { - cache.remove(id); + public static void removeCached(final EntityInterface entity) { + if (entity != null && cache != null) { + cache.remove(entity.getId().toString()); + cache.remove(entity.getIdVersion()); } } @@ -57,14 +58,15 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, S this.entity = entity; } - public RetrieveSparseEntity(final int id) { + public RetrieveSparseEntity(final int id, final String version) { this(new Entity(id)); + this.entity.getVersion().setId(version); } @Override public SparseEntity executeNoCache() throws TransactionException { final RetrieveSparseEntityImpl t = getImplementation(RetrieveSparseEntityImpl.class); - final SparseEntity ret = t.execute(getKey()); + final SparseEntity ret = t.execute(getEntity().getId(), getEntity().getVersion().getId()); if (ret == null) { this.entity.setEntityStatus(EntityStatus.NONEXISTENT); } @@ -78,8 +80,14 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, S } @Override - protected Integer getKey() { - return this.entity.getId(); + protected String getKey() { + if ("HEAD".equalsIgnoreCase(entity.getVersion().getId())) { + return this.entity.getId().toString(); + } else if (entity.hasVersion() + && entity.getVersion().getId().toUpperCase().startsWith("HEAD")) { + return null; + } + return this.entity.getIdVersion(); } public EntityInterface getEntity() { diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java new file mode 100644 index 0000000000000000000000000000000000000000..20be2f8fb60ce54877fdb2fd85d27a6944a71d86 --- /dev/null +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java @@ -0,0 +1,79 @@ +/* + * ** 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 caosdb.server.database.backend.transaction; + +import caosdb.server.database.CacheableBackendTransaction; +import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; +import caosdb.server.database.exceptions.TransactionException; +import caosdb.server.database.proto.VersionHistoryItem; +import caosdb.server.entity.EntityInterface; +import java.util.Collection; +import java.util.HashMap; + +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 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()); + } + + /** 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; + } + + @Override + protected Integer getKey() { + return entity.getId(); + } + + public HashMap<String, VersionHistoryItem> getMap() { + return this.map; + } + + public EntityInterface getEntity() { + return this.entity; + } + + public Collection<VersionHistoryItem> getList() { + return this.map.values(); + } +} diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..1daab0504cafbd05e9e03dff78b2c230a0438ccd --- /dev/null +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java @@ -0,0 +1,84 @@ +/* + * ** 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 caosdb.server.database.backend.transaction; + +import caosdb.datetime.UTCDateTime; +import caosdb.server.database.exceptions.TransactionException; +import caosdb.server.database.proto.VersionHistoryItem; +import caosdb.server.entity.EntityInterface; +import caosdb.server.entity.Version; +import java.util.HashMap; +import java.util.LinkedList; + +public class RetrieveVersionInfo extends RetrieveVersionHistory { + + public RetrieveVersionInfo(EntityInterface e) { + super(e); + } + + @Override + protected void process(HashMap<String, VersionHistoryItem> map) throws TransactionException { + super.process(map); // Make the map available to the object. + if (!map.isEmpty()) 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; + } + + /** 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; + } +} diff --git a/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java b/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java index 6378e8bbbbd8aee87382d226df0691d004e641aa..a7c9ba43f32b1ef97a6a09544041b39a2cd5c71a 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java @@ -53,6 +53,8 @@ public class UpdateEntity extends BackendTransaction { execute(new InsertEntityValue(e)); execute(new InsertEntityProperties(e)); + + execute(new RetrieveVersionInfo(e)); } } } diff --git a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java index c6a1c7683409e0c875484e14553e4aeac2757a71..309ff9cbde0131f4b90cc89301a2338a8b92971f 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java @@ -29,6 +29,7 @@ import caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl; import caosdb.server.database.exceptions.TransactionException; import caosdb.server.database.proto.SparseEntity; import caosdb.server.entity.EntityInterface; +import caosdb.server.entity.Version; public class UpdateSparseEntity extends BackendTransaction { @@ -40,7 +41,7 @@ public class UpdateSparseEntity extends BackendTransaction { @Override public void execute() throws TransactionException { - RetrieveSparseEntity.removeCached(this.entity.getId()); + RetrieveSparseEntity.removeCached(this.entity); if (entity.hasFileProperties()) { GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath()); } @@ -50,5 +51,7 @@ public class UpdateSparseEntity extends BackendTransaction { final SparseEntity spe = this.entity.getSparseEntity(); t.execute(spe); + + this.entity.setVersion(new Version(spe.version)); } } diff --git a/src/main/java/caosdb/server/database/proto/SparseEntity.java b/src/main/java/caosdb/server/database/proto/SparseEntity.java index 4f5dcc69ce5ed7c6afa636b97237b53abb56ab51..d70d4ae026eb72d8c782b763ae3fc21d03a73064 100644 --- a/src/main/java/caosdb/server/database/proto/SparseEntity.java +++ b/src/main/java/caosdb/server/database/proto/SparseEntity.java @@ -38,6 +38,9 @@ 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; @Override public String toString() { @@ -51,6 +54,9 @@ public class SparseEntity extends VerySparseEntity { .append(this.fileHash) .append(this.filePath) .append(this.fileSize) + .append(this.version) + .append(this.versionSeconds) + .append(this.versionNanos) .toString(); } } diff --git a/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java b/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java new file mode 100644 index 0000000000000000000000000000000000000000..e671950f39f40c0b93069d031f636ad964560508 --- /dev/null +++ b/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java @@ -0,0 +1,13 @@ +package caosdb.server.database.proto; + +import java.io.Serializable; +import java.util.LinkedList; + +public class VersionHistoryItem implements Serializable { + + private static final long serialVersionUID = 7855704308135158698L; + public String id = null; + public LinkedList<String> parents = null; + public Long seconds = null; + public Integer nanos = null; +} diff --git a/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java b/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java index ce92aa022cfe85573049eaaeed4e26653b400679..87c17079b0facdc193ea79dd6005598f7fe151b6 100644 --- a/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java +++ b/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java @@ -47,7 +47,7 @@ public class ReferenceDatatype2 extends ReferenceDatatype { } public void setEntity(final EntityInterface datatypeEntity) { - this.refid.setEntity(datatypeEntity); + this.refid.setEntity(datatypeEntity, false); } @Override diff --git a/src/main/java/caosdb/server/datatype/ReferenceValue.java b/src/main/java/caosdb/server/datatype/ReferenceValue.java index 25fa0b381cb436236dd6e3676970cf7e54c17776..77f7aa8c56d97b8dc5abf914c0b6c88749683d69 100644 --- a/src/main/java/caosdb/server/datatype/ReferenceValue.java +++ b/src/main/java/caosdb/server/datatype/ReferenceValue.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -26,27 +28,36 @@ import caosdb.server.datatype.AbstractDatatype.Table; import caosdb.server.entity.EntityInterface; import caosdb.server.entity.Message; import caosdb.server.utils.ServerMessages; +import java.util.Objects; import org.jdom2.Element; +/** + * A ReferenceValue represents the value of a reference property to another entity. + * + * <p>Differently from other properties, they may be versioned, i.e. they may reference to a + * specific version of an entity. + * + * <p>TODO: Ways to specify a reference value, what are the consequences of versioned references? + */ public class ReferenceValue implements SingleValue { private EntityInterface entity = null; private String name = null; private Integer id = null; + private String version = null; + private boolean versioned = false; public static ReferenceValue parseReference(final Object reference) throws Message { if (reference == null) { return null; } if (reference instanceof EntityInterface) { - return new ReferenceValue((EntityInterface) reference); + return new ReferenceValue( + (EntityInterface) reference, ((EntityInterface) reference).hasVersion()); } else if (reference instanceof ReferenceValue) { return (ReferenceValue) reference; } else if (reference instanceof GenericValue) { - try { - return new ReferenceValue(Integer.parseInt(((GenericValue) reference).toDatabaseString())); - } catch (final NumberFormatException e) { - return new ReferenceValue(((GenericValue) reference).toDatabaseString()); - } + String str = ((GenericValue) reference).toDatabaseString(); + return parseFromString(str); } else if (reference instanceof CollectionValue) { throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } else { @@ -58,24 +69,81 @@ public class ReferenceValue implements SingleValue { } } + /** + * Split a reference string into an entity part and a version part, if there is a version part. + * + * <p>If parsing the entity ID part to an integer fails, a NumberFormatException may be thrown. + */ + public static ReferenceValue parseIdVersion(String str) { + String[] split = str.split("@", 2); + if (split.length == 2) { + return new ReferenceValue(Integer.parseInt(split[0]), split[1]); + } else { + return new ReferenceValue(Integer.parseInt(str)); + } + } + + /** + * Create a ReferenceValue from a string. + * + * <p>If the string looks like a valid "entityID@version" string, the result will have the + * corresponding entity and version parts. + */ + public static ReferenceValue parseFromString(String str) { + try { + return parseIdVersion(str); + } catch (final NumberFormatException e) { + return new ReferenceValue(str); + } + } + + /** + * Produce a nice but short string: + * + * <p>Case 1 "versioned" (reference to an entity without specifying that entity's version): + * Produces a string like "1234" or "Experiment". Note that referencing via name is never + * versioned. + * + * <p>Case 2 "unversioned" (reference to an entity with a specified version): Produces a string + * like "1234@ab987f". + */ @Override public String toString() { - if (this.entity != null) { + if (this.entity != null && versioned) { // Was specified as "versioned", with resolved entity + return this.entity.getIdVersion(); + } else if (this.entity != null) { // resolved, but unversioned return this.entity.getId().toString(); - } else if (this.id == null && this.name != null) { + } else if (this.id == null + && this.name != null) { // Only name is available, no id (and thus no resolved entity) return this.name; } + // Specification via id is the only remaining possibility + return getIdVersion(); // if version is null, returns ID only + } + + public String getIdVersion() { + if (this.version != null) { + return new StringBuilder().append(this.id).append("@").append(this.version).toString(); + } return this.id.toString(); } - public ReferenceValue(final EntityInterface entity) { + public ReferenceValue(final EntityInterface entity, boolean versioned) { + this.versioned = versioned; this.entity = entity; } public ReferenceValue(final Integer id) { + this(id, null); + } + + public ReferenceValue(final Integer id, final String version) { this.id = id; + this.version = version; + this.versioned = version != null; } + /** If the reference is given by name, versioning is not possible (at the moment). */ public ReferenceValue(final String name) { this.name = name; } @@ -84,7 +152,8 @@ public class ReferenceValue implements SingleValue { return this.entity; } - public final void setEntity(final EntityInterface entity) { + public final void setEntity(final EntityInterface entity, boolean versioned) { + this.versioned = versioned; this.entity = entity; } @@ -102,6 +171,13 @@ public class ReferenceValue implements SingleValue { return this.id; } + public final String getVersion() { + if (this.entity != null && versioned && this.entity.hasVersion()) { + return this.entity.getVersion().getId(); + } + return this.version; + } + public final void setId(final Integer id) { this.id = id; } @@ -126,7 +202,8 @@ public class ReferenceValue implements SingleValue { if (obj instanceof ReferenceValue) { final ReferenceValue that = (ReferenceValue) obj; if (that.getId() != null && getId() != null) { - return that.getId().equals(getId()); + return that.getId().equals(getId()) + && Objects.deepEquals(that.getVersion(), this.getVersion()); } else if (that.getName() != null && getName() != null) { return that.getName().equals(getName()); } diff --git a/src/main/java/caosdb/server/entity/BaseEntity.java b/src/main/java/caosdb/server/entity/BaseEntity.java deleted file mode 100644 index 62d4873b54c262eb75920f3f5c990e9988f07e50..0000000000000000000000000000000000000000 --- a/src/main/java/caosdb/server/entity/BaseEntity.java +++ /dev/null @@ -1,28 +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 caosdb.server.entity; - -class BaseEntity { - - EntityID id = null; -} diff --git a/src/main/java/caosdb/server/entity/DeleteEntity.java b/src/main/java/caosdb/server/entity/DeleteEntity.java index 29b6a3a075b60e811cfb0e66d066278a47d2edcb..b27ab3a7745a9b4909dac642a9fccbaf035f52f7 100644 --- a/src/main/java/caosdb/server/entity/DeleteEntity.java +++ b/src/main/java/caosdb/server/entity/DeleteEntity.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -27,4 +29,9 @@ public class DeleteEntity extends Entity { public DeleteEntity(final int id) { super(id); } + + public DeleteEntity(int id, String version) { + super(id); + setVersion(new Version(version)); + } } diff --git a/src/main/java/caosdb/server/entity/Entity.java b/src/main/java/caosdb/server/entity/Entity.java index 7fed351c51373e0cdb6aca9fe531c9699c2f9703..16f4f508e2d97a4c0f58c1775e95db9efbf4ffba 100644 --- a/src/main/java/caosdb/server/entity/Entity.java +++ b/src/main/java/caosdb/server/entity/Entity.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -22,6 +24,7 @@ */ package caosdb.server.entity; +import caosdb.datetime.UTCDateTime; import caosdb.server.CaosDBException; import caosdb.server.database.proto.SparseEntity; import caosdb.server.database.proto.VerySparseEntity; @@ -800,7 +803,11 @@ public class Entity extends AbstractObservable implements EntityInterface { final CollectionValue vals = new CollectionValue(); int pidx = 0; for (final Element pe : element.getChildren()) { - if (pe.getName().equalsIgnoreCase("EmptyString")) { + if (pe.getName().equalsIgnoreCase("Version")) { + // IGNORE: Once it becomes allowed for clients to set a version id, parsing + // the Version element would be done here. Until this is the case, the + // Version tag is ignored. + } else if (pe.getName().equalsIgnoreCase("EmptyString")) { // special case: empty string which cannot be distinguished from null // values otherwise. setValue(new GenericValue("")); @@ -1026,6 +1033,7 @@ public class Entity extends AbstractObservable implements EntityInterface { } private boolean datatypeOverride = false; + private Version version = new Version(); @Override public EntityInterface setDatatypeOverride(final boolean b) { @@ -1066,10 +1074,15 @@ public class Entity extends AbstractObservable implements EntityInterface { } @Override - public EntityInterface parseSparseEntity(final SparseEntity spe) { + public final EntityInterface parseSparseEntity(final SparseEntity spe) { setId(spe.id); this.setRole(spe.role); setEntityACL(spe.acl); + UTCDateTime versionDate = null; + if (spe.versionSeconds != null) { + versionDate = UTCDateTime.UTCSeconds(spe.versionSeconds, spe.versionNanos); + } + this.version = new Version(spe.version, versionDate); if (!isNameOverride()) { setName(spe.name); @@ -1118,4 +1131,34 @@ public class Entity extends AbstractObservable implements EntityInterface { public boolean skipJob() { return false; } + + @Override + public Version getVersion() { + return this.version; + } + + @Override + public boolean hasVersion() { + return this.version.getId() != null; + } + + @Override + public void setVersion(Version version) { + this.version = version; + } + + /** Return "id@version" if there is versioning information, else only "id". */ + @Override + public String getIdVersion() { + if (!this.hasId()) { + return null; + } else if (this.hasVersion()) { + return new StringBuilder() + .append(getId()) + .append("@") + .append(getVersion().getId()) + .toString(); + } + return getId().toString(); + } } diff --git a/src/main/java/caosdb/server/entity/EntityID.java b/src/main/java/caosdb/server/entity/EntityID.java deleted file mode 100644 index fbfb83c8028f5f2e0d8376c6dc480c19046c0d5b..0000000000000000000000000000000000000000 --- a/src/main/java/caosdb/server/entity/EntityID.java +++ /dev/null @@ -1,25 +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 caosdb.server.entity; - -public class EntityID {} diff --git a/src/main/java/caosdb/server/entity/EntityInterface.java b/src/main/java/caosdb/server/entity/EntityInterface.java index 20aa57a5b3f64ae5794d01235049c12740a0419b..4205111a58c30185d916c1d921acb7470d93f7a9 100644 --- a/src/main/java/caosdb/server/entity/EntityInterface.java +++ b/src/main/java/caosdb/server/entity/EntityInterface.java @@ -52,6 +52,8 @@ public interface EntityInterface public abstract Integer getId(); + public abstract String getIdVersion(); + public abstract void setId(Integer id); public abstract boolean hasId(); @@ -185,5 +187,11 @@ public interface EntityInterface public abstract void setQueryTemplateDefinition(String query); + public abstract Version getVersion(); + + public abstract boolean hasVersion(); + + public abstract void setVersion(Version version); + public abstract void addToElement(Element element, SetFieldStrategy strategy); } diff --git a/src/main/java/caosdb/server/entity/NamedEntity.java b/src/main/java/caosdb/server/entity/NamedEntity.java deleted file mode 100644 index 76ba79a317e146ca4b86793545f95d9e05e42480..0000000000000000000000000000000000000000 --- a/src/main/java/caosdb/server/entity/NamedEntity.java +++ /dev/null @@ -1,28 +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 caosdb.server.entity; - -class NamedEntity extends BaseEntity { - - String name = null; -} diff --git a/src/main/java/caosdb/server/entity/RetrieveEntity.java b/src/main/java/caosdb/server/entity/RetrieveEntity.java index 5b54c2bcd4ce401bc469728f774ad68198ba0986..7035232b5a66df77518c8c1b586b7bcb43b7a22b 100644 --- a/src/main/java/caosdb/server/entity/RetrieveEntity.java +++ b/src/main/java/caosdb/server/entity/RetrieveEntity.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -22,9 +24,6 @@ */ package caosdb.server.entity; -import caosdb.server.database.proto.SparseEntity; -import caosdb.server.datatype.AbstractCollectionDatatype; - public class RetrieveEntity extends Entity { public RetrieveEntity(final int id) { @@ -35,38 +34,13 @@ public class RetrieveEntity extends Entity { super(name); } - public RetrieveEntity parseSparseEntity(final SparseEntity spe) { - setId(spe.id); - this.setRole(spe.role); - setEntityACL(spe.acl); - - if (!isNameOverride()) { - setName(spe.name); - } - if (!isDescOverride()) { - setDescription(spe.description); - } - if (!isDatatypeOverride()) { - final String dt = spe.datatype; - final String col = spe.collection; - - if (dt != null - && !dt.equalsIgnoreCase("null") - && (!hasDatatype() || !dt.equalsIgnoreCase(getDatatype().toString()))) { - if (col != null && !col.equalsIgnoreCase("null")) { - this.setDatatype(AbstractCollectionDatatype.collectionDatatypeFactory(col, dt)); - } else { - this.setDatatype(dt); - } - } - } - - if (spe.filePath != null) { - setFileProperties(new FileProperties(spe.fileHash, spe.filePath, spe.fileSize)); - } else { - setFileProperties(null); - } + public RetrieveEntity(int id, String version) { + super(id); + this.setVersion(new Version(version)); + } - return this; + public RetrieveEntity(String name, String version) { + super(name); + this.setVersion(new Version(version)); } } diff --git a/src/main/java/caosdb/server/entity/ValidEntity.java b/src/main/java/caosdb/server/entity/ValidEntity.java index 3e99d4ead10152f3ce434a91c8c3d3643cc61273..d2c8b519734f3732f6fc7c43047a99e59ccd71c4 100644 --- a/src/main/java/caosdb/server/entity/ValidEntity.java +++ b/src/main/java/caosdb/server/entity/ValidEntity.java @@ -22,47 +22,9 @@ */ package caosdb.server.entity; -import caosdb.server.database.proto.SparseEntity; -import caosdb.server.datatype.AbstractCollectionDatatype; - public class ValidEntity extends Entity { public ValidEntity(final int id) { super(id); } - - public ValidEntity parseSparseEntity(final SparseEntity spe) { - setId(spe.id); - this.setRole(spe.role); - setEntityACL(spe.acl); - - if (!isNameOverride()) { - setName(spe.name); - } - if (!isDescOverride()) { - setDescription(spe.description); - } - if (!isDatatypeOverride()) { - final String dt = spe.datatype; - final String col = spe.collection; - - if (dt != null - && !dt.equalsIgnoreCase("null") - && (!hasDatatype() || !dt.equalsIgnoreCase(getDatatype().toString()))) { - if (col != null && !col.equalsIgnoreCase("null")) { - this.setDatatype(AbstractCollectionDatatype.collectionDatatypeFactory(col, dt)); - } else { - this.setDatatype(dt); - } - } - } - - if (spe.filePath != null) { - setFileProperties(new FileProperties(spe.fileHash, spe.filePath, spe.fileSize)); - } else { - setFileProperties(null); - } - - return this; - } } diff --git a/src/main/java/caosdb/server/entity/Version.java b/src/main/java/caosdb/server/entity/Version.java new file mode 100644 index 0000000000000000000000000000000000000000..07c7387e8c419f962e1174504a28ceceda5c30e3 --- /dev/null +++ b/src/main/java/caosdb/server/entity/Version.java @@ -0,0 +1,84 @@ +/* + * 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/>. + */ + +package caosdb.server.entity; + +import caosdb.datetime.UTCDateTime; +import java.util.LinkedList; + +/** + * Plain old java object (POJO) for an entity's version. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class Version { + + private String id = null; + private LinkedList<Version> predecessors = null; + private LinkedList<Version> successors = null; + private UTCDateTime date = null; + + public Version(String id, long seconds, int nanos) { + this(id, UTCDateTime.UTCSeconds(seconds, nanos)); + } + + public Version(String id, UTCDateTime date) { + this.id = id; + this.date = date; + } + + public Version(String id) { + this(id, null); + } + + public Version() {} + + public UTCDateTime getDate() { + return date; + } + + public void setDate(UTCDateTime date) { + this.date = date; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LinkedList<Version> getSuccessors() { + return successors; + } + + public void setSuccessors(LinkedList<Version> successors) { + this.successors = successors; + } + + public LinkedList<Version> getPredecessors() { + return predecessors; + } + + public void setPredecessors(LinkedList<Version> predecessors) { + this.predecessors = predecessors; + } +} diff --git a/src/main/java/caosdb/server/entity/container/DeleteContainer.java b/src/main/java/caosdb/server/entity/container/DeleteContainer.java index c219ee5e4399caeee857006f995748435d023963..c3d57bc656a7c9609dd8fb6ca4d031e0e82f63fa 100644 --- a/src/main/java/caosdb/server/entity/container/DeleteContainer.java +++ b/src/main/java/caosdb/server/entity/container/DeleteContainer.java @@ -42,4 +42,9 @@ public class DeleteContainer extends EntityByIdContainer { public void add(final int id) { add(new DeleteEntity(id)); } + + @Override + public void add(int id, String version) { + add(new DeleteEntity(id, version)); + } } diff --git a/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java b/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java index 1bfda2024c4ff11375ad619d64946e1036c714b0..29a0978b8acdc809b75c52de643ee131cd62b129 100644 --- a/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java +++ b/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java @@ -37,4 +37,6 @@ public abstract class EntityByIdContainer extends TransactionContainer { } public abstract void add(int id); + + public abstract void add(int id, String version); } diff --git a/src/main/java/caosdb/server/entity/container/RetrieveContainer.java b/src/main/java/caosdb/server/entity/container/RetrieveContainer.java index 9b4f7364d4ecdc63da1be8f1ead292545801a078..5b205bc27f38e3d1a04b2ed3c5a7e874af229489 100644 --- a/src/main/java/caosdb/server/entity/container/RetrieveContainer.java +++ b/src/main/java/caosdb/server/entity/container/RetrieveContainer.java @@ -46,4 +46,13 @@ public class RetrieveContainer extends EntityByIdContainer { public void add(final String name) { add(new RetrieveEntity(name)); } + + public void add(final String name, String version) { + add(new RetrieveEntity(name, version)); + } + + @Override + public void add(int id, String version) { + add(new RetrieveEntity(id, version)); + } } diff --git a/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java index 056f342c6b09b8f07130e6c89013777809c567b8..f8d563de1776d155b2f66b913de454c7e5b952fd 100644 --- a/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java +++ b/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java @@ -33,6 +33,7 @@ import caosdb.server.entity.FileProperties; import caosdb.server.entity.Message; import caosdb.server.entity.Role; import caosdb.server.entity.StatementStatus; +import caosdb.server.entity.Version; import caosdb.server.entity.container.ParentContainer; import caosdb.server.entity.container.PropertyContainer; import caosdb.server.entity.xml.SetFieldStrategy; @@ -555,6 +556,26 @@ public class EntityWrapper implements EntityInterface { return this.entity.hasPermission(subject, permission); } + @Override + public Version getVersion() { + return this.entity.getVersion(); + } + + @Override + public boolean hasVersion() { + return this.entity.hasVersion(); + } + + @Override + public void setVersion(Version version) { + this.entity.setVersion(version); + } + + @Override + public String getIdVersion() { + return this.entity.getIdVersion(); + } + @Override public void addToElement(Element element, SetFieldStrategy strategy) { this.entity.addToElement(element, strategy); diff --git a/src/main/java/caosdb/server/entity/wrapper/Parent.java b/src/main/java/caosdb/server/entity/wrapper/Parent.java index c36ffa9c2c1a16062add2eb912ca01132af9872c..f3686b5b802e004e55d572e58076e5e08bb5988d 100644 --- a/src/main/java/caosdb/server/entity/wrapper/Parent.java +++ b/src/main/java/caosdb/server/entity/wrapper/Parent.java @@ -57,4 +57,10 @@ public class Parent extends EntityWrapper { public Affiliation getAffiliation() { return this.affiliation; } + + @Override + public boolean hasVersion() { + // parents are not versioned (yet). + return false; + } } diff --git a/src/main/java/caosdb/server/entity/wrapper/Property.java b/src/main/java/caosdb/server/entity/wrapper/Property.java index f6bb840a75d9216ee3fd56f3d2bf95f701280492..1d1cd2a8ae11489479b9e9191365843182d136ca 100644 --- a/src/main/java/caosdb/server/entity/wrapper/Property.java +++ b/src/main/java/caosdb/server/entity/wrapper/Property.java @@ -134,4 +134,10 @@ public class Property extends EntityWrapper { public EntityInterface getDomainEntity() { return this.domain; } + + @Override + public boolean hasVersion() { + // properties are not versioned (yet). + return false; + } } diff --git a/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java index ae69b94024eac0fdd9667233b507545e2b0f09af..6cfe8fa9f3628cc0670ccb3e6f30a263a0bc5cf3 100644 --- a/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java +++ b/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java @@ -87,6 +87,10 @@ public class EntityToElementStrategy implements ToElementStrategy { if (setFieldStrategy.isToBeSet("id") && entity.hasId()) { element.setAttribute("id", Integer.toString(entity.getId())); } + if (setFieldStrategy.isToBeSet("version") && entity.hasVersion()) { + Element v = new VersionXMLSerializer().toElement(entity.getVersion()); + element.addContent(v); + } if (setFieldStrategy.isToBeSet("cuid") && entity.hasCuid()) { element.setAttribute("cuid", entity.getCuid()); } diff --git a/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java b/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java index 3e7e735e6d863721a7591b8a2fc18f6fc9dfa15c..7a4a29a16cc545ca5316c7b086a8b1ec46cfcfc3 100644 --- a/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java +++ b/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java @@ -112,10 +112,10 @@ public class SetFieldStrategy { * are actually used, e.g ["a.b.c.d1", "a.b.c.d2"]. */ return new SetFieldStrategy() { - // Always-true-strategy + // Return true for everything except version fields. @Override public boolean isToBeSet(String field) { - return true; + return field == null || !field.equalsIgnoreCase("version"); } }; } diff --git a/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java b/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..badd1a95f2a2338bde43a1de23774097ab2c45ed --- /dev/null +++ b/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java @@ -0,0 +1,57 @@ +/* + * 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/>. + */ + +package caosdb.server.entity.xml; + +import caosdb.server.entity.Version; +import java.util.TimeZone; +import org.jdom2.Element; + +/** + * Creates a JDOM Element for a Version instance. + * + * @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())); + } + 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); + } + } + 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); + } + } + return result; + } +} diff --git a/src/main/java/caosdb/server/jobs/Job.java b/src/main/java/caosdb/server/jobs/Job.java index 110809e77ae0d40076642d953c15b0d9566eddd6..ff214b977dfd870a9aa56b3e8efa4f12967b2083 100644 --- a/src/main/java/caosdb/server/jobs/Job.java +++ b/src/main/java/caosdb/server/jobs/Job.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -171,11 +173,39 @@ public abstract class Job extends AbstractObservable implements Observer { protected final EntityInterface retrieveValidSparseEntityByName(final String name) throws Message { - return retrieveValidSparseEntityById(retrieveValidIDByName(name)); + return retrieveValidSparseEntityById(retrieveValidIDByName(name), null); } - protected final EntityInterface retrieveValidSparseEntityById(final Integer id) throws Message { - final EntityInterface ret = execute(new RetrieveSparseEntity(id)).getEntity(); + protected final EntityInterface retrieveValidSparseEntityById( + final Integer id, final String version) throws Message { + + String resulting_version = version; + if (version == null || version.equals("HEAD")) { + // the targeted entity version is the entity after the transaction or the + // entity without a specific version. Thus we have to fetch the entity + // from the container if possible. + EntityInterface ret = getEntityById(id); + if (ret != null) { + return ret; + } + } else if (version.startsWith("HEAD~")) { + EntityInterface entById = getEntityById(id); + if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) { + // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be + // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD + // *after* the successful transaction, so that it is consistent with subsequent retrieves. + int offset = Integer.parseInt(version.substring(5)) - 1; + if (offset == 0) { + // special case HEAD~1 + resulting_version = "HEAD"; + } else { + resulting_version = new StringBuilder().append("HEAD~").append(offset).toString(); + } + } + } + + final EntityInterface ret = + execute(new RetrieveSparseEntity(id, resulting_version)).getEntity(); if (ret.getEntityStatus() == EntityStatus.NONEXISTENT) { throw ServerMessages.ENTITY_DOES_NOT_EXIST; } diff --git a/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java index 69d36f42fa2f457b05dcc394659b5c088976bff4..faf481dba5ac16836b4495d6112fc25c887a6f1f 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java +++ b/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java @@ -138,7 +138,8 @@ public final class CheckDatatypePresent extends EntityJob { } } else { - final EntityInterface validDatatypeEntity = retrieveValidSparseEntityById(datatype.getId()); + final EntityInterface validDatatypeEntity = + retrieveValidSparseEntityById(datatype.getId(), null); assertAllowedToUse(validDatatypeEntity); datatype.setEntity(validDatatypeEntity); } @@ -151,7 +152,7 @@ public final class CheckDatatypePresent extends EntityJob { private void checkIfOverride() throws Message { if (getEntity().hasId() && getEntity().getId() > 0) { // get data type from database - final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId()); + final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null); if (foreign.hasDatatype() && !foreign.getDatatype().equals(getEntity().getDatatype())) { // is override! @@ -179,7 +180,7 @@ public final class CheckDatatypePresent extends EntityJob { // the data type of the corresponding abstract property. if (getEntity().hasId() && getEntity().getId() > 0) { // get from data base - final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId()); + final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null); inheritDatatypeFromForeignEntity(foreign); } else if (getEntity().hasId() && getEntity().getId() < 0) { // get from container @@ -218,7 +219,7 @@ public final class CheckDatatypePresent extends EntityJob { for (final EntityInterface parent : getEntity().getParents()) { EntityInterface parentEntity = null; if (parent.getId() > 0) { - parentEntity = retrieveValidSparseEntityById(parent.getId()); + parentEntity = retrieveValidSparseEntityById(parent.getId(), null); } else { parentEntity = getEntityById(parent.getId()); runJobFromSchedule(parentEntity, CheckDatatypePresent.class); diff --git a/src/main/java/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/caosdb/server/jobs/core/CheckParValid.java index 005053a4a7d59c6c9f1a05171085701c606970b5..560558dce6e494fa4857fefadfa7d22ef79ee5aa 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckParValid.java +++ b/src/main/java/caosdb/server/jobs/core/CheckParValid.java @@ -65,7 +65,7 @@ public class CheckParValid extends EntityJob { if (parent.getId() >= 0) { // id >= 0 (parent is yet in the database) // retrieve parent by id - final EntityInterface foreign = retrieveValidSparseEntityById(parent.getId()); + final EntityInterface foreign = retrieveValidSparseEntityById(parent.getId(), null); // check permissions for this // parentforeign.acceptObserver(o) assertAllowedToUse(foreign); diff --git a/src/main/java/caosdb/server/jobs/core/CheckPropValid.java b/src/main/java/caosdb/server/jobs/core/CheckPropValid.java index 6a7cbb51556dd0b9c634da4c8a56d20204fc9f50..5e199e848984a4d042c3a755d83a8d57ba91987f 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckPropValid.java +++ b/src/main/java/caosdb/server/jobs/core/CheckPropValid.java @@ -55,7 +55,7 @@ public class CheckPropValid extends EntityJob { if (property.getId() >= 0) { final EntityInterface abstractProperty = - retrieveValidSparseEntityById(property.getId()); + retrieveValidSparseEntityById(property.getId(), null); assertAllowedToUse(abstractProperty); diff --git a/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java b/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java index fa2b21ccaea7ba3c8bd2b733f8420c213e4f03f7..04a1ac8c5bf6aa42bf2dcec803e6bc1b68a3a7f0 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java +++ b/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java @@ -94,7 +94,8 @@ public class CheckRefidIsaParRefid extends EntityJob { && getEntityByName(rv.getName()).getRole() == Role.File) { } else if (rv.getId() != null && rv.getId() > 0 - && retrieveValidSparseEntityById(rv.getId()).getRole() == Role.File) { + && retrieveValidSparseEntityById(rv.getId(), rv.getVersion()).getRole() + == Role.File) { } else if (rv.getName() != null && retrieveValidSparseEntityByName(rv.getName()).getRole() == Role.File) { } else { diff --git a/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java b/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java index 14d98962f48c300ae77fe0811922bf3b1d7eb4ae..36591070cd64558114bb56e22860b95f42fc6463 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java +++ b/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java @@ -3,7 +3,9 @@ * 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 + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * 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 @@ -85,9 +87,12 @@ public class CheckRefidValid extends EntityJob { private void checkRefValue(final ReferenceValue ref) throws Message { if (ref.getId() != null) { if (ref.getId() >= 0) { - final EntityInterface referencedValidEntity = retrieveValidSparseEntityById(ref.getId()); + final EntityInterface referencedValidEntity = + retrieveValidSparseEntityById(ref.getId(), ref.getVersion()); assertAllowedToUse(referencedValidEntity); - ref.setEntity(referencedValidEntity); + + // link the entity as versioned entity iff the reference specified a version + ref.setEntity(referencedValidEntity, ref.getVersion() != null); } else { @@ -100,7 +105,9 @@ public class CheckRefidValid extends EntityJob { final EntityInterface referencedEntity = getEntityById(ref.getId()); if (referencedEntity != null) { assertAllowedToUse(referencedEntity); - ref.setEntity(referencedEntity); + + // link the entity as versioned entity iff the reference specified a version + ref.setEntity(referencedEntity, ref.getVersion() != null); } else { throw ServerMessages.REFERENCED_ENTITY_DOES_NOT_EXIST; } @@ -117,7 +124,9 @@ public class CheckRefidValid extends EntityJob { if (referencedEntity != null) { assertAllowedToUse(referencedEntity); - ref.setEntity(referencedEntity); + + // link the entity as versioned entity iff the reference specified a version + ref.setEntity(referencedEntity, ref.getVersion() != null); if (checkRefEntity(ref)) { ref.getEntity().acceptObserver(this); } @@ -125,7 +134,9 @@ public class CheckRefidValid extends EntityJob { final EntityInterface referencedValidEntity = retrieveValidSparseEntityByName(ref.getName()); assertAllowedToUse(referencedValidEntity); - ref.setEntity(referencedValidEntity); + + // link the entity as versioned entity iff the reference specified a version + ref.setEntity(referencedValidEntity, ref.getVersion() != null); } } } diff --git a/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java b/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java index a104c124293cf07ecc3b871c4bc6b31470ec2a15..a09f66533cb3bc3c7cc01b2d6931bc120f87f7a7 100644 --- a/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java +++ b/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java @@ -30,17 +30,20 @@ public class RemoveDuplicates extends ContainerJob { @Override protected void run() { - final HashSet<EntityInterface> rm = new HashSet<EntityInterface>(); + // collect duplicates + final HashSet<EntityInterface> duplicates = new HashSet<EntityInterface>(); for (final EntityInterface e : getContainer()) { - if (e.hasId() && !rm.contains(e)) { + if (e.hasId() && !duplicates.contains(e)) { for (final EntityInterface e2 : getContainer()) { - if (e2 != e && e.getId().equals(e2.getId())) { - rm.add(e2); + if (e2 != e && e.getIdVersion().equals(e2.getIdVersion())) { + // this is a duplicate of another entity in this container + duplicates.add(e2); } } } } - for (final EntityInterface e : rm) { + // remove duplicates. + for (final EntityInterface e : duplicates) { getContainer().remove(e); } } diff --git a/src/main/java/caosdb/server/query/Query.java b/src/main/java/caosdb/server/query/Query.java index 4eeaafc03a8f94b0df845aff40a923d1f52dc60a..a2d6e48767f5835bd9bf40ce65717bf9e8363f98 100644 --- a/src/main/java/caosdb/server/query/Query.java +++ b/src/main/java/caosdb/server/query/Query.java @@ -301,7 +301,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac // ... check for RETRIEVE:ENTITY permission... final EntityInterface e = - execute(new RetrieveSparseEntity(q.getKey()), query.getAccess()).getEntity(); + execute(new RetrieveSparseEntity(q.getKey(), null), query.getAccess()).getEntity(); final EntityACL entityACL = e.getEntityACL(); if (!entityACL.isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) { // ... and ignore if not. @@ -555,7 +555,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac while (rs.next()) { final long t1 = System.currentTimeMillis(); final Integer id = rs.getInt("id"); - if (!execute(new RetrieveSparseEntity(id), query.getAccess()) + if (!execute(new RetrieveSparseEntity(id, null), query.getAccess()) .getEntity() .getEntityACL() .isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) { @@ -588,7 +588,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac while (iterator.hasNext()) { final long t1 = System.currentTimeMillis(); final Integer id = iterator.next(); - if (!execute(new RetrieveSparseEntity(id), getAccess()) + if (!execute(new RetrieveSparseEntity(id, null), getAccess()) .getEntity() .getEntityACL() .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) { diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java index d7897450b2caaa7a70f20ead02ed1bb0c67e192d..97488a6ba2a7984d66bb94332b4d7df0f9478cfb 100644 --- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -4,7 +4,8 @@ * * 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,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 @@ -23,7 +24,6 @@ */ package caosdb.server.resource; -import static caosdb.server.utils.Utils.isNonNullInteger; import static java.net.URLDecoder.decode; import caosdb.server.CaosDBException; @@ -38,7 +38,6 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; @@ -77,9 +76,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { private static final XMLParser xmlparser = new XMLParser(); protected String sRID = null; // Server side request ID private String cRID = null; // Client side request ID - private String[] requestedItems = null; - private ArrayList<Integer> requestedIDs = new ArrayList<Integer>(); - private ArrayList<String> requestedNames = new ArrayList<String>(); + private String[] requestedItems = {}; private WebinterfaceUtils utils; /** Return the {@link WebinterfaceUtils} instance for this resource. */ @@ -141,21 +138,6 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } this.requestedItems = specifier.split("&"); - for (final String requestedItem : this.requestedItems) { - if (isNonNullInteger(requestedItem)) { - final int id = Integer.parseInt(requestedItem); - if (id > 0) { - getRequestedIDs().add(id); - } - } else if (requestedItem.equalsIgnoreCase("all")) { - getRequestedNames().clear(); - getRequestedIDs().clear(); - getRequestedNames().add("all"); - break; - } else { - getRequestedNames().add(requestedItem); - } - } } // flags @@ -427,16 +409,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } } - public ArrayList<Integer> getRequestedIDs() { - return this.requestedIDs; - } - - public ArrayList<String> getRequestedNames() { - return this.requestedNames; - } - - public void setRequestedNames(final ArrayList<String> requestedNames) { - this.requestedNames = requestedNames; + public String[] getRequestedItems() { + return this.requestedItems; } public HashMap<String, String> getFlags() { diff --git a/src/main/java/caosdb/server/resource/PermissionRulesResource.java b/src/main/java/caosdb/server/resource/PermissionRulesResource.java index 1610ba372def0b758b76d15d6d839b825a9d96c6..dba1797a03287c503e5898c7e9a7dde6c389630c 100644 --- a/src/main/java/caosdb/server/resource/PermissionRulesResource.java +++ b/src/main/java/caosdb/server/resource/PermissionRulesResource.java @@ -46,7 +46,7 @@ public class PermissionRulesResource extends AbstractCaosDBServerResource { protected Representation httpGetInChildClass() throws ConnectionException, IOException, SQLException, CaosDBException, NoSuchAlgorithmException, Exception { - final String role = getRequestedNames().get(0); + final String role = getRequestedItems()[0]; getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_PERMISSIONS(role)); @@ -73,7 +73,7 @@ public class PermissionRulesResource extends AbstractCaosDBServerResource { public Representation httpPutInChildClass(final Representation entity) throws Exception { final Element root = parseEntity(entity).getRootElement(); - final String role = getRequestedNames().get(0); + final String role = getRequestedItems()[0]; final HashSet<PermissionRule> rules = new HashSet<PermissionRule>(); for (final Element e : root.getChildren()) { diff --git a/src/main/java/caosdb/server/resource/RolesResource.java b/src/main/java/caosdb/server/resource/RolesResource.java index ef9b30f5b831454fac392317e2c57833657c62d6..0f1b01e3230cf3e73f1c20949f38e180bd8f7e3b 100644 --- a/src/main/java/caosdb/server/resource/RolesResource.java +++ b/src/main/java/caosdb/server/resource/RolesResource.java @@ -52,8 +52,8 @@ public class RolesResource extends AbstractCaosDBServerResource { final Element root = generateRootElement(); final Document document = new Document(); - if (!getRequestedNames().isEmpty()) { - final String name = getRequestedNames().get(0); + if (getRequestedItems().length > 0) { + final String name = getRequestedItems()[0]; if (name != null) { getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(name)); final RetrieveRoleTransaction t = new RetrieveRoleTransaction(name); @@ -78,8 +78,8 @@ public class RolesResource extends AbstractCaosDBServerResource { protected Representation httpDeleteInChildClass() throws ConnectionException, SQLException, CaosDBException, IOException, NoSuchAlgorithmException, Exception { - if (!getRequestedNames().isEmpty()) { - final String name = getRequestedNames().get(0); + if (getRequestedItems().length > 0) { + final String name = getRequestedItems()[0]; if (name != null) { final DeleteRoleTransaction t = new DeleteRoleTransaction(name); try { @@ -133,26 +133,28 @@ public class RolesResource extends AbstractCaosDBServerResource { protected Representation httpPutInChildClass(final Representation entity) throws ConnectionException, JDOMException, Exception, xmlNotWellFormedException { - final String name = getRequestedNames().get(0); - String description = null; + if (getRequestedItems().length > 0) { + final String name = getRequestedItems()[0]; + String description = null; - final Form f = new Form(entity); - if (!f.isEmpty()) { - description = f.getFirstValue("role_description"); - } + final Form f = new Form(entity); + if (!f.isEmpty()) { + description = f.getFirstValue("role_description"); + } - if (name != null && description != null) { - final Role role = new Role(); - role.name = name; - role.description = description; - final UpdateRoleTransaction t = new UpdateRoleTransaction(role); - try { - t.execute(); - } catch (final Message m) { - if (m == ServerMessages.ROLE_DOES_NOT_EXIST) { - return error(m, Status.CLIENT_ERROR_NOT_FOUND); - } else { - throw m; + if (name != null && description != null) { + final Role role = new Role(); + role.name = name; + role.description = description; + final UpdateRoleTransaction t = new UpdateRoleTransaction(role); + try { + t.execute(); + } catch (final Message m) { + if (m == ServerMessages.ROLE_DOES_NOT_EXIST) { + return error(m, Status.CLIENT_ERROR_NOT_FOUND); + } else { + throw m; + } } } } diff --git a/src/main/java/caosdb/server/resource/UserResource.java b/src/main/java/caosdb/server/resource/UserResource.java index b77ff8a49035d2be930a3f957e743ed2f0dae067..7dc9111ee332421fd27d1c32632cc458cbf6c800 100644 --- a/src/main/java/caosdb/server/resource/UserResource.java +++ b/src/main/java/caosdb/server/resource/UserResource.java @@ -60,9 +60,9 @@ public class UserResource extends AbstractCaosDBServerResource { final Document doc = new Document(); final Element rootElem = generateRootElement(); - if (!getRequestedNames().isEmpty()) { + if (getRequestedItems().length > 0) { try { - final String username = getRequestedNames().get(0); + final String username = getRequestedItems()[0]; final String realm = (getRequestAttributes().containsKey("realm") ? (String) getRequestAttributes().get("realm") @@ -92,7 +92,7 @@ public class UserResource extends AbstractCaosDBServerResource { try { final Form form = new Form(entity); - final String username = getRequestedNames().get(0); + final String username = getRequestedItems()[0]; final String realm = (getRequestAttributes().containsKey("realm") ? (String) getRequestAttributes().get("realm") @@ -187,7 +187,8 @@ public class UserResource extends AbstractCaosDBServerResource { final Document doc = new Document(); final Element rootElem = generateRootElement(); - final DeleteUserTransaction t = new DeleteUserTransaction(getRequestedNames().get(0)); + final String username = getRequestedItems()[0]; + final DeleteUserTransaction t = new DeleteUserTransaction(username); try { t.execute(); } catch (final Message m) { diff --git a/src/main/java/caosdb/server/resource/UserRolesResource.java b/src/main/java/caosdb/server/resource/UserRolesResource.java index 27e147157675982f036b88981b1dce221abae754..46d51ca4d60bec89b584cefc362e54ab37063503 100644 --- a/src/main/java/caosdb/server/resource/UserRolesResource.java +++ b/src/main/java/caosdb/server/resource/UserRolesResource.java @@ -47,7 +47,7 @@ public class UserRolesResource extends AbstractCaosDBServerResource { protected Representation httpGetInChildClass() throws ConnectionException, IOException, SQLException, CaosDBException, NoSuchAlgorithmException, Exception { - final String user = getRequestedNames().get(0); + final String user = getRequestedItems()[0]; final String realm = (getRequestAttributes().get("realm") != null ? (String) getRequestAttributes().get("realm") @@ -73,7 +73,7 @@ public class UserRolesResource extends AbstractCaosDBServerResource { @Override protected Representation httpPutInChildClass(final Representation entity) throws ConnectionException, JDOMException, Exception, xmlNotWellFormedException { - final String user = getRequestedNames().get(0); + final String user = getRequestedItems()[0]; final String realm = (getRequestAttributes().get("realm") != null ? (String) getRequestAttributes().get("realm") diff --git a/src/main/java/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/caosdb/server/resource/transaction/EntityResource.java index dc25bcff3418b3f46f90e965a6b80cdf3111c07f..70e7bdf73adbf5e950fe0a51e119a8dbbe2e054f 100644 --- a/src/main/java/caosdb/server/resource/transaction/EntityResource.java +++ b/src/main/java/caosdb/server/resource/transaction/EntityResource.java @@ -30,8 +30,8 @@ import caosdb.server.entity.container.RetrieveContainer; import caosdb.server.entity.container.UpdateContainer; import caosdb.server.resource.AbstractCaosDBServerResource; import caosdb.server.resource.transaction.handlers.FileUploadHandler; -import caosdb.server.resource.transaction.handlers.IDHandler; import caosdb.server.resource.transaction.handlers.RequestHandler; +import caosdb.server.resource.transaction.handlers.SimpleDeleteRequestHandler; import caosdb.server.resource.transaction.handlers.SimpleGetRequestHandler; import caosdb.server.resource.transaction.handlers.SimpleWriteHandler; import caosdb.server.transaction.Delete; @@ -72,7 +72,7 @@ public class EntityResource extends AbstractCaosDBServerResource { } protected RequestHandler<DeleteContainer> getDeleteRequestHandler() { - return new IDHandler<DeleteContainer>(); + return new SimpleDeleteRequestHandler(); } protected RequestHandler<InsertContainer> getPostRequestHandler() { diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java deleted file mode 100644 index e641ed416c86075d23a37d70ad791c26a820a966..0000000000000000000000000000000000000000 --- a/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java +++ /dev/null @@ -1,36 +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 caosdb.server.resource.transaction.handlers; - -import caosdb.server.entity.container.EntityByIdContainer; -import caosdb.server.resource.transaction.EntityResource; - -public class IDHandler<T extends EntityByIdContainer> extends RequestHandler<T> { - - @Override - public void handle(final EntityResource t, final T container) throws Exception { - for (final int id : t.getRequestedIDs()) { - container.add(id); - } - } -} diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..7fb416451bd8f053df5231eb1002db0c03544f99 --- /dev/null +++ b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java @@ -0,0 +1,55 @@ +/* + * ** 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) 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 caosdb.server.resource.transaction.handlers; + +import caosdb.server.entity.container.DeleteContainer; +import caosdb.server.resource.transaction.EntityResource; + +public class SimpleDeleteRequestHandler extends RequestHandler<DeleteContainer> { + + @Override + public void handle(final EntityResource t, final DeleteContainer container) throws Exception { + // TODO a lot of code duplication, see SimpleGetRequestHandle#handle. + // However, this is about to be changed again when string ids are + // introduced, anyways. So we just leave it. + for (final String item : t.getRequestedItems()) { + String[] elem = item.split("@", 1); + Integer id = null; + String version = null; + try { + id = Integer.parseInt(elem[0]); + } catch (NumberFormatException e) { + // pass + } + if (elem.length > 1) { + version = elem[1]; + } + + if (id != null) { + container.add(id, version); + } + } + } +} diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java index 8fbb9d25cb33d2d0a75ce8405a164c1e8c9ce0fd..18c89d46966a3e22b1a9066b84dcf2d05efd122c 100644 --- a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java +++ b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java @@ -25,13 +25,29 @@ package caosdb.server.resource.transaction.handlers; import caosdb.server.entity.container.RetrieveContainer; import caosdb.server.resource.transaction.EntityResource; -public class SimpleGetRequestHandler extends IDHandler<RetrieveContainer> { +public class SimpleGetRequestHandler extends RequestHandler<RetrieveContainer> { @Override public void handle(final EntityResource t, final RetrieveContainer container) throws Exception { - super.handle(t, container); - for (final String name : t.getRequestedNames()) { - container.add(name); + for (final String item : t.getRequestedItems()) { + String[] elem = item.split("@", 2); + Integer id = null; + String name = null; + String version = null; + try { + id = Integer.parseInt(elem[0]); + } catch (NumberFormatException e) { + name = elem[0]; + } + if (elem.length > 1) { + version = elem[1]; + } + + if (id != null) { + container.add(id, version); + } else { + container.add(name); + } } } } diff --git a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java index fa51d5ff49c9693d2216fd5dc168873f850e6e25..faeb96e02738395647abb75fca3eaafe18040ad3 100644 --- a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java +++ b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java @@ -166,8 +166,11 @@ public class ServerSideScriptingCaller { if (f.getPath() == null || f.getPath().isEmpty()) { throw new CaosDBException("The path must not be null or empty!"); } - caosdb.server.utils.FileUtils.createSymlink( - getUploadFilesDir().toPath().resolve(f.getPath()).toFile(), f.getFile()); + File link = getUploadFilesDir().toPath().resolve(f.getPath()).toFile(); + if (!link.getParentFile().exists()) { + link.getParentFile().mkdirs(); + } + caosdb.server.utils.FileUtils.createSymlink(link, f.getFile()); } } diff --git a/src/main/java/caosdb/server/transaction/ChecksumUpdater.java b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java index 30313478c263a131658005daa39da3b495c85eb2..29603fc5833837dfef4a16e8886cdca995c41b94 100644 --- a/src/main/java/caosdb/server/transaction/ChecksumUpdater.java +++ b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java @@ -141,7 +141,7 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl instance.running = false; return null; } - return execute(new RetrieveSparseEntity(id), weakAccess).getEntity(); + return execute(new RetrieveSparseEntity(id, null), weakAccess).getEntity(); } } catch (final Exception e) { e.printStackTrace(); diff --git a/src/main/java/caosdb/server/transaction/Insert.java b/src/main/java/caosdb/server/transaction/Insert.java index 627ada52655c5e7802e98172d1c32f8280a95fb4..c1d53f3051eb65efd9d658179e514e13863c313b 100644 --- a/src/main/java/caosdb/server/transaction/Insert.java +++ b/src/main/java/caosdb/server/transaction/Insert.java @@ -26,6 +26,7 @@ import caosdb.server.database.access.Access; import caosdb.server.database.backend.transaction.InsertEntity; import caosdb.server.entity.EntityInterface; import caosdb.server.entity.FileProperties; +import caosdb.server.entity.Version; import caosdb.server.entity.container.InsertContainer; import caosdb.server.entity.container.TransactionContainer; import caosdb.server.permissions.EntityACL; @@ -103,6 +104,10 @@ 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())); + } } } diff --git a/src/main/java/caosdb/server/transaction/WriteTransaction.java b/src/main/java/caosdb/server/transaction/WriteTransaction.java index e67ae0600bc573efbdf67b9b341299fe1fe3ca00..ec37d19d6f5a4663c0af0adda186c282215a302b 100644 --- a/src/main/java/caosdb/server/transaction/WriteTransaction.java +++ b/src/main/java/caosdb/server/transaction/WriteTransaction.java @@ -64,4 +64,8 @@ public abstract class WriteTransaction<C extends TransactionContainer> extends T } } } + + public String getSRID() { + return getContainer().getRequestId(); + } } diff --git a/src/main/java/caosdb/server/utils/Utils.java b/src/main/java/caosdb/server/utils/Utils.java index 085be51d0929ce76d10ed5e4e8c32696e0b48476..752d828d0e89e790aa7d42e1358b91d3fb5f3c1b 100644 --- a/src/main/java/caosdb/server/utils/Utils.java +++ b/src/main/java/caosdb/server/utils/Utils.java @@ -47,24 +47,6 @@ public class Utils { /** Secure random number generator, for secret random numbers. */ private static final SecureRandom srand = new SecureRandom(); - /** - * Check whether obj is non-null and can be parsed to an integer. - * - * @param obj The object to check. - * @return true if obj is not null and obj.toString() can be parsed as an integer - */ - public static boolean isNonNullInteger(final Object obj) { - if (obj == null) { - return false; - } - try { - Integer.parseInt(obj.toString()); - } catch (final NumberFormatException e) { - return false; - } - return true; - } - /** * Regular expression pattern that checks a mail for RFC822 compliance. * diff --git a/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java b/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java index 7e0feec8915283170b2964420d3ef1f6a42c0031..0d60ebd70feebe2e8e8810f296908e8b3a74100e 100644 --- a/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java +++ b/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java @@ -89,7 +89,7 @@ public class PropertyToElementStrategyTest { PropertyToElementStrategy strategy = new PropertyToElementStrategy(); SetFieldStrategy setFieldStrategy = new SetFieldStrategy().addSelection(parse("height")); EntityInterface property = windowProperty; - ((ReferenceValue) property.getValue()).setEntity(window); + ((ReferenceValue) property.getValue()).setEntity(window, true); Element element = strategy.toElement(property, setFieldStrategy); assertEquals("Property", element.getName()); assertEquals("2345", element.getAttributeValue("id"));