Class JobRequestAction

java.lang.Object
io.jenkins.plugins.batchcontrol.action.JobRequestAction
All Implemented Interfaces:
Action, ModelObject

@Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public class JobRequestAction extends Object implements Action
Per-job "Request Run" page at /job/<name>/batch-control/ (attached by JobRequestActionFactory). Renders the request form — reason, approver set and the job's parameter definitions exactly like the core build page — and submits it to RunRequestService.create(hudson.model.Job<?, ?>, java.util.List<hudson.model.ParameterValue>, java.lang.String, java.lang.String).

The sidebar link is only visible when run control is on, the job requires approval and the user holds BatchControl/Request. Without BatchControl/Request the action is absent altogether (getUrlName() returns null, SPEC item 2, S-07): the form exposes the eligible approver user-id list, which is not for plain Item/Read holders, so /job/<name>/batch-control/ and every URL beneath it answer 404.

  • Constructor Details

    • JobRequestAction

      public JobRequestAction(Job<?,?> job)
  • Method Details

    • getJob

      public Job<?,?> getJob()
    • getObject

      public Job<?,?> getObject()
      The job, under the name core's l:job-subpage reads (it.object), so the request form renders as a sub-page of the job in both the classic and the new job page.
    • getIconFileName

      @CheckForNull public String getIconFileName()
      Specified by:
      getIconFileName in interface Action
    • isCanRequestRun

      public boolean isCanRequestRun()
      Whether the current user may submit a run request for this job: BatchControl/Request and Item/Read on the job (D-38a; Item/Build is not required), the checks RunRequestService.create(hudson.model.Job<?, ?>, java.util.List<hudson.model.ParameterValue>, java.lang.String, java.lang.String) makes. View gating only; doSubmit(org.kohsuke.stapler.StaplerRequest2, org.kohsuke.stapler.StaplerResponse2) and the service check for real.
    • getDisplayName

      public String getDisplayName()
      The one sidebar entry on a controlled job that reads "Request Run" (U-02): core's relabeled build link is named RequestRunUiDecorator.BLOCKED_BUILD_LABEL so the two cannot be confused.
      Specified by:
      getDisplayName in interface Action
      Specified by:
      getDisplayName in interface ModelObject
    • getGroup

      public Group getGroup()
      Hosting review 2026-10-02: on the new job page the run request is the job's build button, so it sits first in the app bar rather than in the overflow menu. Only where the entry is shown at all (getIconFileName()); core's own BuildJobAction is in the same group and reads RequestRunUiDecorator.BLOCKED_BUILD_LABEL on a controlled job.
      Specified by:
      getGroup in interface Action
    • getSemantic

      public Semantic getSemantic()
      Rendered as a build action (the build colour) on the new job page; see getGroup().
      Specified by:
      getSemantic in interface Action
    • getUrlName

      @CheckForNull public String getUrlName()
      null without BatchControl/Request: per Action.getUrlName() that makes the action unreachable, so its whole URL space answers 404 (absent, not refused). doSubmit(org.kohsuke.stapler.StaplerRequest2, org.kohsuke.stapler.StaplerResponse2) re-checks the permission on top of this. Also null on a job's sub-item (a matrix configuration, a Maven module, D-82): run requests are made on its parent job, whose refusal page and notices link that job's form.
      Specified by:
      getUrlName in interface Action
    • getDialogUrl

      public String getDialogUrl()
      D-66: root-relative URL of the dialog form (dialog.jelly), which the classic sidebar entry (action.jelly) opens in core's dialog.
    • getEvent

      public Event getEvent()
      D-66: on the new job page the entry opens the request form in core's dialog, as core's own "Build with Parameters" does (DialogEvent); relative to the job's URL. The full page stays for the refused direct build (D-60) and direct links.
      Specified by:
      getEvent in interface Action
    • checkDialogPermission

      public void checkDialogPermission()
      D-66: the check of the dialog view (dialog.jelly, rendered without a layout): the permission of getViewPermissions(), on the job.
    • getViewPermissions

      public Permission[] getViewPermissions()
      Permissions for the request form's l:layout.
    • isActive

      public boolean isActive()
      True when run control is on and this job requires approved runs. Never on a job's sub-item (D-82): it has no approval property and no run requests of its own.
    • getParameterDefinitions

      public List<ParameterDefinition> getParameterDefinitions()
      The job's parameter definitions, rendered by each definition's own index.jelly.

      e2e-03 DEF-09: when a submission is being refused, each definition whose value was parsed is replaced by a copy defaulting to that value (ParameterDefinition.copyWithDefaultValue(hudson.model.ParameterValue)), so the re-rendered form shows what the user entered. Sensitive values (password parameters) and file values are never copied back into the page (D-72: the form says to provide them again, getReenterParameterNames()).

    • isPrefilled

      public boolean isPrefilled()
      D-60: whether the form shows values carried from a refused build submission.
    • getReenterParameterNames

      public List<String> getReenterParameterNames()
      The names of the job's parameters whose values the form never fills in, so the user provides them again (D-72, D-60): file parameters (RequestRunPrefill.isFileDefinition(hudson.model.ParameterDefinition)) and password parameters. Shown on a refused submission, on a refused direct build (D-60; files are not carried, issue #10) and on a rerun that continues here.
    • getFileParameterNames

      public List<String> getFileParameterNames()
    • getRerunIncidentId

      @CheckForNull public String getRerunIncidentId()
      D-72, D-72a (SPEC item 11): the id of the incident whose rerun continues on this form, or null. On a GET it comes from "fromRerun"=<id>; on a refused submission it is the reference doSubmit(org.kohsuke.stapler.StaplerRequest2, org.kohsuke.stapler.StaplerResponse2) validated (the hidden field of the same name, or the action's query string when the body was over the size cap and never read), and nothing is read from the body here. Either way the id counts only when IncidentService.linkableIncident(java.lang.String, hudson.model.Job<?, ?>) accepts it (an existing incident of this job, and a viewer holding BatchControl/ViewHistory who may request a run of the job, the rights of the rerun itself), the same rule doSubmit(org.kohsuke.stapler.StaplerRequest2, org.kohsuke.stapler.StaplerResponse2) links the request by; anything else is ignored, so a crafted link shows no notice and reveals nothing. The form then says which values must be provided again and that the request will be linked to the incident, and carries the id back in the hidden field; the values that could be recovered arrive as p.<name> like a refused build's (D-60).
    • getRerunReason

      public String getRerunReason()
      The reason the form starts with on a rerun that continues here (getRerunIncidentId()): the reason the rerun would have generated, editable; otherwise empty. No argument on purpose: Stapler would bind a one-argument getter to a URL.
    • submitQuery

      public String submitQuery(boolean dialog)
      D-66, D-72a: the query string of the request form's action, with its ?, or empty: dialog=true for the dialog form (Dialogs.fromDialogQuery(jakarta.servlet.http.HttpServletRequest)) and "fromRerun"=<id> when the form continues an incident rerun (getRerunIncidentId(), already validated). Both travel in the query string as well as in hidden fields so that a submission refused before its body is read (over the size cap) is still answered with the right view and the incident reference; doSubmit(org.kohsuke.stapler.StaplerRequest2, org.kohsuke.stapler.StaplerResponse2) reads the hidden field first and validates the reference again wherever it comes from.
    • getFormErrors

      public FormErrors getFormErrors()
      The refusal of the last submission on this request, or an empty one (DEF-09).
    • getApproverOptions

      public List<String> getApproverOptions()
      Approver candidates: global approver list ∩ job-level restriction (if configured).
    • isShowMarkReviewed

      public boolean isShowMarkReviewed()
      D-58b (3): whether this page offers "Mark as reviewed": the job is in the "changed under a grant" state (itself or through an ancestor, GrantService.isChangedUnderGrant(hudson.model.Item)) and the viewer holds Item/Configure on it. doMarkReviewed() re-checks, and GrantService.markReviewed(hudson.model.Item) refuses a user whose Configure comes from a grant.
    • getMarkedRuns

      public List<ReplayedRuns.Row> getMarkedRuns()
      D-58c: the runs of this job replayed under a permission window, for the review section.
    • doMarkReviewed

      public org.kohsuke.stapler.HttpResponse doMarkReviewed()
      POST markReviewed (D-58b (3)): the deliberate review of this job after changes made under a permission window. GrantService.markReviewed(hudson.model.Item) refuses a user whose Configure comes from a grant and writes the GUARD_REVIEWED record; this page only asks for Item/Configure first. Redirects back to this page with a short confirmation.
    • isReviewedNotice

      public boolean isReviewedNotice()
      Whether the page shows the "marked as reviewed" confirmation (constant text only).
    • doSubmit

      public void doSubmit(org.kohsuke.stapler.StaplerRequest2 req, org.kohsuke.stapler.StaplerResponse2 rsp) throws IOException, jakarta.servlet.ServletException
      POST submit — creates the run request and redirects to its detail page at /batch-control/requests/<id>/. A refused submission re-renders the form with HTTP 400, the message next to the field it concerns and the user's input kept (e2e-03 DEF-09).

      D-72: the form posts multipart/form-data, so file parameters are uploaded with it, and the request keeps the submitted values with their types (create). Because requesting a run does not require Item/Build (D-38a), the size is checked in two stages (D-74 (2), RequestBodyLimit), each answered with HTTP 413 and the empty form, nothing created or kept:

      1. right after the permission checks and before this endpoint reads the body, the declared Content-Length (RequestBodyLimit.exceeds(jakarta.servlet.http.HttpServletRequest)), an early filter that a body without a declared length passes;
      2. before anything is stored, what the request would keep (RequestBodyLimit.checkKept(java.util.List<? extends hudson.model.ParameterValue>, java.util.Map<java.lang.String, java.lang.Long>), in create): the values plus the uploaded parts each was created from, measured here (RequestBodyLimit.uploadedSize(org.kohsuke.stapler.StaplerRequest2, java.util.Collection<java.lang.String>)) before its createValue, since creating a value may consume its part.
      (Core's own dispatch to this URL may already have parsed a multipart body: Job#getDynamic builds the job's widgets and HistoryWidget reads a paging parameter. That happens for every URL under a job, before any plugin code, and is bounded only by Stapler's org.kohsuke.stapler.RequestImpl.FILEUPLOAD_MAX_* properties.) When the form's own checks refuse a submission, the temporary files of its file values are disposed of here (ParameterFiles); once the values are handed to the service, it disposes of them on its own refusals. The re-rendered form never shows a password or a file again.

      D-72a (SPEC item 11): a submission from the form an incident rerun fell back to carries the incident id in the field "fromRerun". It is read after the permission and size checks, passed through IncidentService.linkableIncident(java.lang.String, hudson.model.Job<?, ?>) and only the id that check returns goes to create, which links the request so that a successful run records resolvedByRunId; an invalid reference is ignored. A refused submission keeps the validated reference on the re-rendered form.

      N-01: parseParameters(org.kohsuke.stapler.StaplerRequest2, net.sf.json.JSONObject, java.util.List<hudson.model.ParameterValue>, java.util.Map<java.lang.String, java.lang.Long>) is inside the try on purpose. A parameter definition rejects a bad value by throwing IllegalArgumentException — a choice parameter given a value outside its choices, for instance — and that is the same class the service's own validation throws. Parsing outside the try turned user-supplied input into an uncaught exception and an HTTP 500 "Oops!" page while an empty reason correctly answered 400; both are user input and both belong in the same 400 channel, now the re-rendered form.

      security-08 S-10: a scripted submission with no json field used to store an empty parameter map, so the later approved build ran with whatever defaults the job had at build time rather than the values the approver saw. parseRawParameters(org.kohsuke.stapler.StaplerRequest2, java.util.List<hudson.model.ParameterValue>, java.util.Map<java.lang.String, java.lang.Long>) now reads each defined parameter straight from the request the way core's ParametersDefinitionProperty#_doBuild / buildWithParameters do, so the stored values are complete either way.

      D-72b: a parameter name submitted more than once in the json field is refused here, before the service sees any value, as a field error below that parameter (parameterField(java.lang.String)); on the raw channel each definition reads its own field, and a definition that refuses a repeated field is reported the same way. The service's own refusals are filed by fileServiceRefusal(io.jenkins.plugins.batchcontrol.ui.FormErrors, java.lang.RuntimeException): a message about one of the job's parameters below it, the reason's next to the reason, and a failed save (StoreWriteException) above the form, always as the re-rendered form (HTTP 400), never an error page. A request over the kept-size cap (RequestTooLargeException) is answered with HTTP 413, like an oversized body.

      Throws:
      IOException
      jakarta.servlet.ServletException
    • parameterField

      public String parameterField(String name)
      D-72b: the FormErrors field of the parameter named name, whose message the form shows right below that parameter (_form.jelly). Not a getter on purpose: a one-argument getter would be bound to a URL.
    • selectedRunId

      @CheckForNull public String selectedRunId(ParameterDefinition definition)
      T-06-103 (D-60, DEF-09): the run the form shows selected for the run parameter definition, or null to render the definition's own view (RequestRunPrefill.selectedRunId(hudson.model.ParameterDefinition); core's view does not show a carried run). Not a getter on purpose, like parameterField(java.lang.String).