From 82f48d15a390562b835f33b176945743b1e61816 Mon Sep 17 00:00:00 2001 From: Timm Fitschen <t.fitschen@indiscale.com> Date: Thu, 4 Jun 2020 00:55:57 +0200 Subject: [PATCH] WIP: versioning phase 8 --- .../backend/transaction/RetrieveParents.java | 13 ++++ .../transaction/RetrieveSparseEntity.java | 3 +- .../server/datatype/ReferenceDatatype2.java | 2 +- .../server/datatype/ReferenceValue.java | 64 ++++++++++++++++--- .../java/caosdb/server/entity/Entity.java | 6 +- src/main/java/caosdb/server/jobs/Job.java | 32 +++++++++- .../jobs/core/CheckDatatypePresent.java | 9 +-- .../server/jobs/core/CheckParValid.java | 2 +- .../server/jobs/core/CheckPropValid.java | 2 +- .../jobs/core/CheckRefidIsaParRefid.java | 3 +- .../server/jobs/core/CheckRefidValid.java | 19 ++++-- src/main/java/caosdb/server/query/Query.java | 6 +- .../server/transaction/ChecksumUpdater.java | 2 +- 13 files changed, 131 insertions(+), 32 deletions(-) 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 9fffcae8..eac5aa7f 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java @@ -34,6 +34,18 @@ 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 slips through +// unnoticed. +// +// Changes are to be made 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<String, ArrayList<VerySparseEntity>> { @@ -67,6 +79,7 @@ public class RetrieveParents @Override protected void process(final ArrayList<VerySparseEntity> t) throws TransactionException { this.entity.getParents().clear(); + DatabaseUtils.parseParentsFromVerySparseEntity(this.entity, t); } 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 6ad58df8..1eb0c4c5 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java @@ -58,8 +58,9 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<String, Sp 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 diff --git a/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java b/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java index ce92aa02..87c17079 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 25fa0b38..3fa22c94 100644 --- a/src/main/java/caosdb/server/datatype/ReferenceValue.java +++ b/src/main/java/caosdb/server/datatype/ReferenceValue.java @@ -26,27 +26,28 @@ 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; 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,17 +59,46 @@ public class ReferenceValue implements SingleValue { } } + 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)); + } + } + + public static ReferenceValue parseFromString(String str) { + try { + return parseIdVersion(str); + } catch (final NumberFormatException e) { + return new ReferenceValue(str); + } + } + @Override public String toString() { - if (this.entity != null) { + if (this.entity != null && versioned) { + return this.entity.getIdVersion().toString(); + } else if (this.entity != null) { return this.entity.getId().toString(); } else if (this.id == null && this.name != null) { return this.name; + } else if (this.version != null) { + return getIdVersion(); } return this.id.toString(); } - public ReferenceValue(final EntityInterface entity) { + 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, boolean versioned) { + this.versioned = versioned; this.entity = entity; } @@ -76,6 +106,11 @@ public class ReferenceValue implements SingleValue { this.id = id; } + public ReferenceValue(final Integer id, final String version) { + this.id = id; + this.version = version; + } + public ReferenceValue(final String name) { this.name = name; } @@ -84,7 +119,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 +138,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 +169,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/Entity.java b/src/main/java/caosdb/server/entity/Entity.java index 9bd0b6dd..23fb170d 100644 --- a/src/main/java/caosdb/server/entity/Entity.java +++ b/src/main/java/caosdb/server/entity/Entity.java @@ -1142,7 +1142,11 @@ public class Entity extends AbstractObservable implements EntityInterface { if (!this.hasId()) { return null; } else if (this.hasVersion()) { - return new StringBuilder().append(getId()).append(getVersion().getId()).toString(); + return new StringBuilder() + .append(getId()) + .append("@") + .append(getVersion().getId()) + .toString(); } return getId().toString(); } diff --git a/src/main/java/caosdb/server/jobs/Job.java b/src/main/java/caosdb/server/jobs/Job.java index 110809e7..8d98cbc3 100644 --- a/src/main/java/caosdb/server/jobs/Job.java +++ b/src/main/java/caosdb/server/jobs/Job.java @@ -171,11 +171,37 @@ 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~")) { + // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is + // part of this request (i.e. is to be updated), the actual offset has to be + // reduced by 1. HEAD always denotes the entity@HEAD *after* the successful + // transaction. + 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 69d36f42..faf481db 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 005053a4..560558dc 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 6a7cbb51..5e199e84 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 fa2b21cc..04a1ac8c 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 14d98962..0139b98f 100644 --- a/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java +++ b/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java @@ -85,9 +85,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 +103,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 +122,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 +132,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/query/Query.java b/src/main/java/caosdb/server/query/Query.java index 1f858269..c235a34e 100644 --- a/src/main/java/caosdb/server/query/Query.java +++ b/src/main/java/caosdb/server/query/Query.java @@ -299,7 +299,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. @@ -553,7 +553,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)) { @@ -586,7 +586,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/transaction/ChecksumUpdater.java b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java index 30313478..29603fc5 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(); -- GitLab