Class FileStore

java.lang.Object
io.jenkins.plugins.batchcontrol.store.FileStore
All Implemented Interfaces:
Store

@Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public final class FileStore extends Object implements Store
File-based Store rooted at $JENKINS_HOME/batch-control/.
  • Entities with state (requests, grants, incidents) are XStream XML files, rewritten atomically (temp file, then ATOMIC_MOVE).
  • Append-only records (runs, changes) are monthly JSONL files, written with append + flush; Instants are stored as epoch milliseconds.
  • Writes are serialized per target file (a fixed set of lock stripes): an append to this month's bucket never waits for anything but another single write that happens to share its stripe, and no operation holds a lock across more than one file write or deletion. In particular retention, which deletes old months and closed entities file by file, cannot stall the queue gate or build completion.
  • Page loads read JSONL buckets newest first and stop at a record cap; the month summary counters and the entity index are derived in-memory caches, never files.
  • File names derived from identifiers are validated through PathCodec; month and id names use ASCII digits whatever the default locale.
  • Method Details

    • get

      public static FileStore get()
      The file store instance. Callers go through Store.get() (D-44); only that method and the tests of this class name FileStore directly.
    • saveRunRequest

      public void saveRunRequest(RunRequest request)
      Description copied from interface: Store
      Writes (or rewrites, on a status transition) the request XML <id>.xml atomically. Never touches the request's typed values (D-74).
      Specified by:
      saveRunRequest in interface Store
    • saveNewRunRequest

      public void saveNewRunRequest(RunRequest request, List<ParameterValue> values)
      Stores a new request (D-72, D-74): <id>.xml, then its typed values in <id>.values.xml when there are any (a request without parameters has no values file). Each file is written atomically; when either cannot be written, neither is left behind.

      The request file is written first: should the process stop between the two writes, what remains is a request whose values are missing, which cannot be approved (fail closed), and never a values file without its request.

      Specified by:
      saveNewRunRequest in interface Store
    • loadRunRequest

      public RunRequest loadRunRequest(String id)
      Description copied from interface: Store
      Loads a request by id from <id>.xml, or returns null if it does not exist. Its typed values are never read here (D-74): this is the read for screens, badges, listings, listeners and periodic work.
      Specified by:
      loadRunRequest in interface Store
    • loadRunRequestValues

      @CheckForNull public List<ParameterValue> loadRunRequestValues(String id)
      Description copied from interface: Store
      The typed values of request id from <id>.values.xml (D-74), or null when there is no values file (the request has no parameters, or its values were deleted when its run started or it ended). Only for approving, submitting, recovering and disposing of a request. An element that could not be loaded may be null.
      Specified by:
      loadRunRequestValues in interface Store
    • deleteRunRequestValues

      public void deleteRunRequestValues(String id)
      Description copied from interface: Store
      Deletes the typed values of request id (<id>.values.xml), when the approved run starts or the request ends (D-72b (5), D-74); nothing happens when there are none.
      Specified by:
      deleteRunRequestValues in interface Store
    • listRunRequests

      public List<RunRequest> listRunRequests()
      Description copied from interface: Store
      Loads every stored run request, sorted by creation time. A file that cannot be read (corrupt, or not readable at all) is left out with a warning, so one such file never breaks a listing.
      Specified by:
      listRunRequests in interface Store
    • listOpenRunRequests

      public List<RunRequest> listOpenRunRequests(Consumer<String> unreadable)
      Description copied from interface: Store
      As Store.listOpenRunRequests(), and hands the id of every open request left out because its file cannot be read to unreadable, for a caller that must act on it once it can be read (an invalidation it would otherwise miss).
      Specified by:
      listOpenRunRequests in interface Store
    • hasRunRequestBy

      public boolean hasRunRequestBy(String userId)
      Description copied from interface: Store
      Whether at least one stored run request, in any status, was filed by userId (D-38b). Answered from the in-memory entity index; no request file and no item is read.
      Specified by:
      hasRunRequestBy in interface Store
    • listRunRequestSummaries

      public List<RequestSummary> listRunRequestSummaries()
      Description copied from interface: Store
      The listing fields of every stored run request, sorted by id, from memory. A listing filters and pages on these and loads only the XML of the rows it renders.
      Specified by:
      listRunRequestSummaries in interface Store
    • saveGrantRequest

      public void saveGrantRequest(GrantRequest request)
      Description copied from interface: Store
      Writes (or rewrites, on a status transition) the grant request XML atomically.
      Specified by:
      saveGrantRequest in interface Store
    • loadGrantRequest

      public GrantRequest loadGrantRequest(String id)
      Description copied from interface: Store
      Loads a grant request by id, or returns null if it does not exist.
      Specified by:
      loadGrantRequest in interface Store
    • listGrantRequests

      public List<GrantRequest> listGrantRequests()
      Description copied from interface: Store
      Loads every stored grant request, sorted by id (creation order).
      Specified by:
      listGrantRequests in interface Store
    • listOpenGrantRequests

      public List<GrantRequest> listOpenGrantRequests()
      Description copied from interface: Store
      Loads the PENDING grant requests only, sorted by id, via the entity index. An open request whose file cannot be read is left out with a warning and its index entry kept, as for Store.listOpenRunRequests().
      Specified by:
      listOpenGrantRequests in interface Store
    • saveGrant

      public void saveGrant(Grant grant)
      Description copied from interface: Store
      Writes (or rewrites, on revocation) the grant XML atomically.
      Specified by:
      saveGrant in interface Store
    • loadGrant

      public Grant loadGrant(String id)
      Description copied from interface: Store
      Loads a grant by id, or returns null if it does not exist.
      Specified by:
      loadGrant in interface Store
    • listGrants

      public List<Grant> listGrants()
      Description copied from interface: Store
      Loads every stored grant, sorted by id (creation order). A file that cannot be read (corrupt, or not readable at all) is left out with a warning: that grant confers nothing, and the permission checks of everyone else keep working.
      Specified by:
      listGrants in interface Store
    • saveActivationRequest

      public void saveActivationRequest(ActivationRequest request)
      Description copied from interface: Store
      Writes (or rewrites, on a status transition) the activation request XML atomically.
      Specified by:
      saveActivationRequest in interface Store
    • loadActivationRequest

      public ActivationRequest loadActivationRequest(String id)
      Description copied from interface: Store
      Loads an activation request by id, or returns null if it does not exist.
      Specified by:
      loadActivationRequest in interface Store
    • listActivationRequests

      public List<ActivationRequest> listActivationRequests()
      Description copied from interface: Store
      Loads every stored activation request, sorted by id (creation order).
      Specified by:
      listActivationRequests in interface Store
    • hasActivationRequestBy

      public boolean hasActivationRequestBy(String userId)
      Description copied from interface: Store
      Whether at least one stored activation or hold request, in any status, was filed by userId (D-38b). Answered from the in-memory entity index.
      Specified by:
      hasActivationRequestBy in interface Store
    • listOpenActivationRequests

      public List<ActivationRequest> listOpenActivationRequests(Consumer<String> unreadable)
      Description copied from interface: Store
      As Store.listOpenActivationRequests(), and hands the id of every open request left out because its file cannot be read to unreadable.
      Specified by:
      listOpenActivationRequests in interface Store
    • saveActivationState

      public void saveActivationState(ActivationState state)
      Description copied from interface: Store
      Writes the activation state of a job atomically to activations/<PathCodec-encoded full name>.xml; the file name comes from ActivationState.getJobFullName().
      Specified by:
      saveActivationState in interface Store
    • loadActivationState

      public ActivationState loadActivationState(String jobFullName)
      Description copied from interface: Store
      The stored activation state of a job, or null if none exists.
      Specified by:
      loadActivationState in interface Store
    • listActivationStates

      public List<ActivationState> listActivationStates()
      Description copied from interface: Store
      Every stored activation state (unordered).
      Specified by:
      listActivationStates in interface Store
    • deleteActivationState

      public boolean deleteActivationState(String jobFullName)
      Description copied from interface: Store
      Removes the activation state of a job; true if one existed.
      Specified by:
      deleteActivationState in interface Store
    • isActivationSchemaMarked

      public boolean isActivationSchemaMarked()
      Description copied from interface: Store
      Whether the one-time upgrade seeding marker activations/.schema exists.
      Specified by:
      isActivationSchemaMarked in interface Store
    • markActivationSchema

      public void markActivationSchema()
      Description copied from interface: Store
      Writes the upgrade seeding marker activations/.schema (atomically).
      Specified by:
      markActivationSchema in interface Store
    • saveConfigSnapshot

      public void saveConfigSnapshot(String jobFullName, String configXml)
      Description copied from interface: Store
      Stores the latest config.xml snapshot of a job (diff baseline; only the latest version is kept, ARCHITECTURE section 5).
      Specified by:
      saveConfigSnapshot in interface Store
    • loadConfigSnapshot

      public String loadConfigSnapshot(String jobFullName)
      Description copied from interface: Store
      The stored config snapshot text of a job, or null if none exists.
      Specified by:
      loadConfigSnapshot in interface Store
    • hasConfigSnapshot

      public boolean hasConfigSnapshot(String jobFullName)
      Description copied from interface: Store
      Whether anything is stored where the config snapshot of a job belongs: a snapshot, or something else in its place (which Store.loadConfigSnapshot(java.lang.String) refuses). Nothing is read, so seeding the missing snapshots of every item costs one file system lookup per item.
      Specified by:
      hasConfigSnapshot in interface Store
    • deleteConfigSnapshot

      public void deleteConfigSnapshot(String jobFullName)
      Description copied from interface: Store
      Removes the config snapshot of a job (after deletion, rename or move).
      Specified by:
      deleteConfigSnapshot in interface Store
    • appendRunRecord

      public void appendRunRecord(RunRecord record)
      Description copied from interface: Store
      Appends one run record to the monthly JSONL bucket derived from its start time. The line carries appendedAt (D-81): the record's own RunRecord.getAppendedAt() when the caller set it, else the plugin clock's instant of this append.
      Specified by:
      appendRunRecord in interface Store
    • listRunRecords

      public List<RunRecord> listRunRecords(YearMonth month)
      Description copied from interface: Store
      Reads every run record of the given month (empty list if the month file is absent).
      Specified by:
      listRunRecords in interface Store
    • pageRunRecords

      public RecordPage<RunRecord> pageRunRecords(Collection<YearMonth> months, Period period, Predicate<? super RunRecord> filter, int offset, int limit, int maxScanned)
      Description copied from interface: Store
      One page of the run records of months that match filter, newest first (start time, then run id). Reads newest first and stops after maxScanned records, so the cost of a page load is bounded whatever a month holds; only records whose start time lies inside period count toward maxScanned (security-10 S-03).
      Specified by:
      pageRunRecords in interface Store
      Parameters:
      offset - matches to skip (page index times page size)
      limit - rows to return
      maxScanned - records to read at most; see Store.MAX_SCANNED_RECORDS
    • runMonthStats

      public RunMonthStats runMonthStats(YearMonth month)
      Description copied from interface: Store
      Run counters of one month, maintained incrementally from the bucket.
      Specified by:
      runMonthStats in interface Store
    • appendChangeRecord

      public void appendChangeRecord(ChangeRecord record)
      Description copied from interface: Store
      Appends one change record to the monthly JSONL bucket derived from its timestamp.
      Specified by:
      appendChangeRecord in interface Store
    • listChangeRecords

      public List<ChangeRecord> listChangeRecords(YearMonth month)
      Description copied from interface: Store
      Reads every change record of the given month (empty list if the month file is absent).
      Specified by:
      listChangeRecords in interface Store
    • pageChangeRecords

      public RecordPage<ChangeRecord> pageChangeRecords(Collection<YearMonth> months, Predicate<? super ChangeRecord> filter, int offset, int limit, int maxScanned)
      Description copied from interface: Store
      Specified by:
      pageChangeRecords in interface Store
    • pageChangeRecords

      public RecordPage<ChangeRecord> pageChangeRecords(Collection<YearMonth> months, Period period, Predicate<? super ChangeRecord> filter, int offset, int limit, int maxScanned)
      Description copied from interface: Store
      Specified by:
      pageChangeRecords in interface Store
    • grantRevokeRecordsSince

      public RecordPage<ChangeRecord> grantRevokeRecordsSince(Instant since, Set<String> grantIds, Predicate<? super ChangeRecord> boundary)
      S-39-03: every GRANT_REVOKE change record of a grant in grantIds with a time at or after since, newest first, for re-ending windows at startup (D-74). Bounded by time, not by a record count: the change log is read back only to the first record appended before since, and every record after it is read, however many there are. The page is truncated when that could not be done completely.

      Reading also stops at the first record before since that boundary accepts (T-SEC-109): the caller's statement that no line appended before that record can be the end of any window in grantIds, so the append-order slack is not read past it.

      The bounded page query with no record cap and no page limit: only the period bounds it, so it reads every line since since (and the lines inside the append-order slack before it), keeping only the matching records, which are few. It is truncated only when the bytes of skipped out-of-period lines exceed the page query's byte budget. No diff is attached.

      D-75 (2): it is truncated too when a line it read could not be parsed (a torn or damaged line) or was too long to read: such a line may be a GRANT_REVOKE record of one of the windows, so their ends cannot be ruled out, and the caller ends them (fail-closed). Its position tells that it was appended within the part of the log read back. Logged once per read. A complete line of another record type, also one this version does not know, is not such a record and does not count.

      T-SEC-109: reading also stops at the first record before since that boundary accepts, inside the append-order slack too, so damage behind such a record is not read again.

      Specified by:
      grantRevokeRecordsSince in interface Store
    • createIncident

      public void createIncident(Incident incident)
      Description copied from interface: Store
      Persists a freshly opened incident: writes incidents/<id>.xml and appends the monthly index line (incidents/index/YYYY-MM.jsonl, bucket derived from the incident's creation time).
      Specified by:
      createIncident in interface Store
    • saveIncident

      public void saveIncident(Incident incident)
      Description copied from interface: Store
      Rewrites an existing incident's XML atomically (handling transitions, links).
      Specified by:
      saveIncident in interface Store
    • loadIncident

      public Incident loadIncident(String id)
      Description copied from interface: Store
      Loads an incident by id, or returns null if it does not exist.
      Specified by:
      loadIncident in interface Store
    • listIncidents

      public List<Incident> listIncidents(YearMonth month)
      Description copied from interface: Store
      Loads every incident created in the given month via the monthly index, in index (creation) order. Index lines whose XML has been deleted are skipped.
      Specified by:
      listIncidents in interface Store
    • pageIncidents

      public RecordPage<Incident> pageIncidents(Collection<YearMonth> months, Period period, Predicate<? super IncidentSummary> indexFilter, Predicate<? super Incident> filter, int offset, int limit, int maxScanned)
      Description copied from interface: Store
      As Store.pageRunRecords(java.util.Collection<java.time.YearMonth>, io.jenkins.plugins.batchcontrol.store.Period, java.util.function.Predicate<? super io.jenkins.plugins.batchcontrol.model.RunRecord>, int, int, int) for incidents, walking the monthly index newest first (creation time, then id); every index line read counts against maxScanned. indexFilter is tested on the index line and an incident's XML is loaded only when it passes (security-10 S-06); filter then sees the loaded incident.
      Specified by:
      pageIncidents in interface Store
    • listStoredMonths

      public List<YearMonth> listStoredMonths()
      Description copied from interface: Store
      Every month for which any record bucket exists (runs, changes or incident index), sorted ascending. Used by retention cleanup.
      Specified by:
      listStoredMonths in interface Store
    • listStoredMonths

      public List<YearMonth> listStoredMonths(Period period)
      Description copied from interface: Store
      #33: the stored month buckets (as Store.listStoredMonths()) that may hold a record whose timestamp lies in period, whatever time zone the plugin clock had when the record was written: a bucket is named by the month of the record in the zone of its writer (SPEC 4), so after the controller's zone changed, a record near a month boundary sits in the neighbouring month's bucket. A date-range query reads these buckets and keeps the records inside period; existing files need no migration. A bucket outside the period's own months is included only when the period comes within the largest zone offset (18 hours) of it.
      Specified by:
      listStoredMonths in interface Store
    • deleteMonth

      public boolean deleteMonth(YearMonth month)
      Deletes one month bucket file by file: every incident XML, patch and bucket file is deleted under its own lock stripe, and the lock is released before the next one, so no writer waits behind more than a single deletion. Incident XMLs and patches go first and the JSONL files last, so an interrupted pass leaves the index in place and the next pass finishes it.
      Specified by:
      deleteMonth in interface Store
      Returns:
      true if anything was deleted
    • deleteClosedEntitiesBefore

      public RetentionResult deleteClosedEntitiesBefore(Instant cutoff)
      Description copied from interface: Store
      Retention of the XML entities: deletes closed run requests (not PENDING or APPROVED) and closed grant requests (not PENDING) whose last activity lies before cutoff, and grants that stopped conferring anything (expiry or revocation) before it. A grant request is kept while a grant issued from it is kept. Each file is deleted under its own lock, so no store lock is held for longer than one deletion.
      Specified by:
      deleteClosedEntitiesBefore in interface Store
    • warmUp

      public void warmUp()
      Builds the entity index now (startup), so no later save pays for it.
      Specified by:
      warmUp in interface Store