Class CurationLock

java.lang.Object
ubic.gemma.model.common.auditAndSecurity.curation.CurationLock
All Implemented Interfaces:
Serializable

@Entity public class CurationLock extends Object implements Serializable
An advisory, steal-able claim on a dataset's curation.

One row per currently locked dataset, deleted on release. The primary key is the investigation itself, so a dataset cannot be locked twice.

🛑 Advisory, and it must stay that way. The correctness guarantee for concurrent curation writes is the optimistic-concurrency token — PUT /datasets/{id}/curation checks baseline.lastModified and returns 409 when the dataset moved. This lock exists so that 409 rarely fires and so a curator can see who else is working. It gates exactly one thing: sign-off, which is the destructive act. Editing and committing stay advisory. If the baseline check is ever removed because "the lock handles it", that is the bug.

🛑 Never write this through a curatable update path, and never emit an audit event for it. Any audit event sets curationDetails.lastUpdated (AbstractCuratableDao.updateCurationDetailsFromAuditEvent), and that field is the concurrency token above — so taking a lock would 409 every in-flight draft on the dataset. That is the bug bebe778980 fixed for snapshots. stolenFrom / stolenAt are the record of a steal, for the same reason the SNAPSHOT row is its own record of a capture.

Stealing is always permitted. It loses no work: the displaced curator's DRAFT is a separate AnnotationSet row and is untouched, so the cost of a steal is that the displaced curator's next commit 409s on a stale baseline and they re-sync.

Expiry is not swept. An acquire treats expiresAt in the past as free and overwrites the row, so an abandoned tab frees itself with no cleanup job to run, monitor and forget.

See Also:
  • Constructor Details

    • CurationLock

      public CurationLock()
  • Method Details

    • getRunId

      @Nullable public String getRunId()
    • setRunId

      public void setRunId(@Nullable String runId)
    • getAgentName

      @Nullable public String getAgentName()
    • setAgentName

      public void setAgentName(@Nullable String agentName)
    • getInvestigationId

      @Nullable public Long getInvestigationId()
      Returns:
      the primary key, which is the locked investigation's id.
    • getInvestigation

      public Investigation getInvestigation()
    • setInvestigation

      public void setInvestigation(Investigation investigation)
    • getLockedBy

      public String getLockedBy()
    • setLockedBy

      public void setLockedBy(String lockedBy)
    • getLockedAt

      public Date getLockedAt()
    • setLockedAt

      public void setLockedAt(Date lockedAt)
    • getExpiresAt

      public Date getExpiresAt()
    • setExpiresAt

      public void setExpiresAt(Date expiresAt)
    • getStolenFrom

      @Nullable public String getStolenFrom()
    • setStolenFrom

      public void setStolenFrom(@Nullable String stolenFrom)
    • getStolenAt

      @Nullable public Date getStolenAt()
    • setStolenAt

      public void setStolenAt(@Nullable Date stolenAt)
    • isExpired

      public boolean isExpired(Date now)
      Parameters:
      now - the reference instant, passed in rather than read from the clock so callers can evaluate a batch of locks against one consistent moment
      Returns:
      whether the claim has lapsed. An expired lock is treated as free by an acquire; nothing sweeps it.
    • isHeldBy

      public boolean isHeldBy(@Nullable String username, Date now)
      Returns:
      whether username currently holds this lock — false once it has expired, so a returning curator re-acquires rather than silently resuming a lapsed claim someone else may have taken.