Class QuestionStore

java.lang.Object
io.jenkins.plugins.interactiveinput.store.QuestionStore

@Extension public class QuestionStore extends Object
In-memory registry of human-in-the-loop Questions with XStream persistence to $JENKINS_HOME/interactive-input/questions.xml (§8.1, §8.2).

The store is the single authority for question lifecycle and permissions. Paused pipeline steps register a transient QuestionStore.Resolution keyed by question id; when a question reaches a terminal state (answered, aborted, expired) the store invokes that resolution so the pipeline resumes. The question metadata survives a Jenkins restart via XStream; the resolution is re-registered when the step's onResume runs.

Concurrency: the questions map is a ConcurrentHashMap; all transitions on a single Question are serialised via synchronized (question) (§8.9).

  • Field Details

  • Constructor Details

    • QuestionStore

      public QuestionStore()
  • Method Details

    • get

      @NonNull public static QuestionStore get()
    • submit

      @NonNull public Question submit(@NonNull Question question)
      Register a new WAITING question and persist it.
      Returns:
      the same question for convenience
    • register

      public void register(@NonNull String questionId, @NonNull QuestionStore.Resolution resolution)
      Attach a paused step's resolver. If the question already reached a terminal state (e.g. it was answered while the step was being resumed after a restart), the resolver is invoked immediately.
    • unregister

      public void unregister(@NonNull String questionId)
    • answer

      @NonNull public Answer answer(@NonNull String questionId, @CheckForNull String choiceId, @CheckForNull String freeText, @NonNull String byUserId, @NonNull String source)
      Record a human answer and resume the pipeline. Callers must pre-check permissions (a 403 is the REST/UI layer's responsibility); this method assumes the caller is authorised.
      Throws:
      IllegalStateException - if the question is missing or already settled
    • answerParameters

      @NonNull public Answer answerParameters(@NonNull String questionId, @NonNull Map<String,Object> parameterValues, @NonNull String byUserId, @NonNull String source)
      Record an input-style parameter answer and resume the pipeline (B24). Callers must pre-check permissions and pre-validate/convert the values (the REST/UI layer's responsibility); this method assumes the caller is authorised and the values are already the resolved ParameterValue.getValue() objects. Only parameter names are logged — never values, so a password parameter is not leaked.
      Throws:
      IllegalStateException - if the question is missing or already settled
    • abort

      public void abort(@NonNull String questionId, @NonNull String byUserId, @NonNull String source)
      Abort a question (cancel the input). Delivers an abort to the pipeline.
      Throws:
      IllegalStateException - if the question is missing or already settled
    • abortForShutdown

      public void abortForShutdown(@NonNull String questionId, @NonNull String byUserId)
      Transition a WAITING question to ABORTED without invoking its resolver. Used when the framework stops the step itself (e.g. the build is aborted) and will deliver the cause to the pipeline directly. No-op if the question is missing or already terminal.
    • get

      @CheckForNull public Question get(@NonNull String questionId)
    • remove

      public void remove(@NonNull String questionId)
      Remove a question outright (used by the input-step bridge to drop a mirror when the underlying native input has settled or the bridge has been disabled).
    • listAnswerable

      @NonNull public List<Question> listAnswerable()
      Returns:
      all WAITING questions the current user is allowed to answer (bell + default REST list).
    • listReadable

      @NonNull public List<Question> listReadable()
      Returns:
      all WAITING questions the current user can at least read (Item.READ on the source job).
    • listAll

      @NonNull public List<Question> listAll()
      Returns:
      every WAITING question (admin only; callers must enforce Overall/Administer).
    • listAnswerableForJob

      @NonNull public List<Question> listAnswerableForJob(@NonNull String jobFullName)
      Returns:
      WAITING questions for jobFullName the current user may answer.
    • listReadableForJob

      @NonNull public List<Question> listReadableForJob(@NonNull String jobFullName)
      Returns:
      WAITING questions for jobFullName the current user can at least read.
    • countAnswerableForJob

      public int countAnswerableForJob(@NonNull String jobFullName)
      Returns:
      the number of WAITING questions for jobFullName the current user may answer.
    • listForBuild

      @NonNull public List<Question> listForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      every question (any status) recorded for a specific build that the current user can read. Used by the per-build audit view; includes settled questions until compaction.
    • hasWaitingForBuild

      public boolean hasWaitingForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      true if jobFullName #buildNumber has any WAITING question.
    • hasAnyForBuild

      public boolean hasAnyForBuild(@NonNull String jobFullName, int buildNumber)
      Existence probe (no permission filter) used by the run-action factory to decide whether to attach the per-build surfaces. Anyone reaching a build page already holds Item.READ, so this leaks nothing beyond what the audit view (which is permission-checked) would show.
      Returns:
      true if any question (any status) is recorded for this build.
    • listNotifications

      @NonNull public List<Question> listNotifications()
      Returns:
      WAITING questions to surface for the current user across all jobs.
    • listNotificationsForJob

      @NonNull public List<Question> listNotificationsForJob(@NonNull String jobFullName)
      Returns:
      WAITING questions to surface for the current user, scoped to one job.
    • countNotifications

      public int countNotifications()
    • countNotificationsForJob

      public int countNotificationsForJob(@NonNull String jobFullName)
    • countNotificationsForBuild

      public int countNotificationsForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      the number of WAITING questions to surface for the current user on a single build, honouring the user-scope and lock-to-starter switches. Drives the run-page sidebar count badge (the per-build equivalent of countNotificationsForJob(String)).
    • hasNotificationForBuild

      public boolean hasNotificationForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      true if the build has a WAITING question the current user should be notified of (drives the build-history badge, honouring user-scope and lock).
    • canAnswer

      public boolean canAnswer(@NonNull Question q)
      Returns:
      true if the current authentication may answer the question.
    • canAbort

      public boolean canAbort(@NonNull Question q)
      Returns:
      true if the current authentication may abort the question (and thereby abort the run). Requires Item.CANCEL on the source job, or — when a submitterFilter is set — membership in that set. Jenkins.ADMINISTER and security-off always pass. Item.BUILD alone is not enough: aborting a pipeline is Cancel, not Build.
    • canAnswerEffective

      public boolean canAnswerEffective(@NonNull Question q)
      Returns:
      true if the current authentication may answer after applying the lock-to-build-starter switch. This is canAnswer(Question) unless the switch is on, in which case only the build starter (or a Jenkins administrator) may answer; builds with no human starter are never locked. This is the check the REST answer endpoint and the modal's canAnswer flag use — it can only ever restrict, never widen, access.
    • canAbortEffective

      public boolean canAbortEffective(@NonNull Question q)
      Returns:
      true if the current authentication may abort after applying the lock-to-build-starter switch. Same restriction as canAnswerEffective(Question): the lock can only ever restrict, never widen, Cancel.
    • canView

      public boolean canView(@NonNull Question q)
      Returns:
      true if the current authentication can read the source job (Item.READ).
    • findJob

      @CheckForNull public Job<?,?> findJob(@NonNull Question q)
    • currentUserId

      @NonNull public static String currentUserId()
      Returns:
      the user id of the current authentication, or "SYSTEM" if unauthenticated.
    • expireOverdue

      public void expireOverdue(long now)
      Expire every overdue WAITING question. Called by the SLA ticker.
    • compact

      public void compact(long now, long retentionMs)
      Remove terminal questions older than retentionMs. Called by the SLA ticker.
    • removeForBuild

      public int removeForBuild(@NonNull String jobFullName, int buildNumber)
      Purge every question of a now-deleted build (any status). A question is meaningful only alongside its build — its audit trail lives on the build page, which is gone — so on build deletion its questions are removed outright, clearing them from the notification centre and the sidebar/badge counts. Invoked by the RunListener when a build is deleted.
      Returns:
      the number of questions removed.
    • reconcileDeletedBuilds

      public int reconcileDeletedBuilds()
      Startup self-heal: purge questions whose owning build no longer exists (deleted before this cleanup shipped, or while the controller was down). Only acts when the job still resolves but the build is gone, so a temporarily-unresolvable job never loses its questions. Invoked once from BuildLifecycleCleanup's onLoaded.
      Returns:
      the number of questions removed.
    • choice

      @NonNull public static Choice choice(@NonNull String id, @NonNull String label, @CheckForNull String why)