Form validation

Often you want to check the correctness of the values entered by the user on the fly. This is called "form validation", and Jenkins performs this on the server side.

You write a form validation logic as a method on your Descriptor class, and it’ll look something like this:

public FormValidation doCheckThreads(@QueryParameter String value) throws IOException, ServletException {
  try {
    Integer.parseInt(value);
    return FormValidation.ok();
  } catch (NumberFormatException e) {
    return FormValidation.error("Not a number");
  }
}

The name of the method follows the convention doCheckXyz where xyz is the name of the field you put in your view. The method gets invoked in response to the onchange event on HTML DOM. So the above check method should have a corresponding view like this:

<f:entry title="Number of Threads" field="threads">
  <f:textbox />
</f:entry>

The parameter name value is also significant. The throws clause isn’t. If you want to use a different name as variable, e.g. meaningfulName you can do so, with:

public FormValidation doCheckThreads(@QueryParameter("value") final String meaningfulName) throws IOException, ServletException {
  // ...
}

This naming convention is how Jenkins wires up your form validation method. The return type also must be FormValidation, which offers various static factory methods to create an instance. Form validation can be of 3 different kinds: ok, warning or error. Validation message can either be represented in plaintext or use HTML markup. Several validations of different kinds can be reported wrapping then into one using aggregate factory method.

Getting values of other fields

Sometimes your validation method needs values of other input fields to perform a check. You do this by defining them as additional parameters:

public FormValidation doCheckThreads(@QueryParameter String value, @QueryParameter String cpu) {
  try {
    int t = Integer.parseInt(value);
    int c = Integer.parseInt(cpu);
    if (t*c>10) {
      return FormValidation.warning("That's asking for too much");
    } else {
      return FormValidation.ok();
    }
  } catch (NumberFormatException e) {
    return FormValidation.error("Not a number");
  }
}
<f:entry title="Number of Threads" field="threads">
  <f:textbox />
</f:entry>
<f:entry title="Number of CPUs" field="cpu">
  <f:textbox />
</f:entry>

The parameter names are used to match up the Java method parameters to the form values. (So you could have used the parameter names "threads" instead of "value" — "value" is a reserved keyword that designates the "this input control")

When multiple parameters are defined like this, the validation kicks in whenever one of the dependent values is changed.

Relative path

Used in conjunction with QueryParameter to refer to nearby parameters that belong to different parents. ".." refers to values in the parent object, and "foo" refers to the child object of the current object named "foo". They can be combined with '/' like path, such as "../foo/bar", "../..", and etc. A good way to think about this is the file system structure. @RelativePath is like the dirname, and QueryParameter is like the basename. Together they form a relative path. And because of the structured form submissions, form elements are organized in a tree structure of JSON objects, which is akin to directories and files. The relative path then points from the current input element (for which you are doing form validation, for example) to the target input element that you want to obtain the value.

public FormValidation doCheckThreads(
    @QueryParameter String value,
    @RelativePath("../../parent")
    @QueryParameter String cpu
) {
  try {
    int t = Integer.parseInt(value);
    int c = Integer.parseInt(cpu);
    if (t*c>10) {
      return FormValidation.warning("That's asking for too much");
    } else {
      return FormValidation.ok();
    }
  } catch (NumberFormatException e) {
    return FormValidation.error("Not a number");
  }
}

Accessing context

Sometimes you want to access the context. For example, you might want to access the current FreeStyleProject object while validating a field of a Builder. You do this by putting AncestorInPath annotation.

public FormValidation doCheckThreads(@QueryParameter String value, @AncestorInPath AbstractProject project) {
  ...
}

References