Workflow validators are specific validators that checks whether some pre-defined constraints are satisfied or not while progressing on a workflow.The constraints are configured in the workflow and the user will get an error if the same is not satisfied. A typical example would be to check if a particular field is present or not before the status of the issue is moved to a different status.
As usual, have your skeleton plugin ready. Let us start with the atlassian-plugin.xml which you can find in your skeleton plugin.
The workflow validator module in the plugin descriptor looks like this:
class="com.jtricks.FieldValidatorFactory">
<description>Field Not Empty Workflow Validator</description>
<validator-class>
com.jtricks.FieldValidator
</validator-class>
<resource type="velocity" name="view"
location="templates/com/jtricks/view-fieldValidator.vm"/>
<resource type="velocity" name="input-parameters"
location="templates/com/jtricks/edit-fieldValidator.vm"/>
<resource type="velocity" name="edit-parameters"
location="templates/com/jtricks/edit-fieldValidator.vm"/>
</workflow-validator>
Now we come to the next important class which is the validator-class. This is the place where the actual validation is done and we need to implement Validator here. We will more about both these classes shortly.
We also have 3 views in a validator depending on your requirement (Much similar to post functions and conditions!). view, input-parameters and edit-parameters. As you might have guessed already, input-parameters and edit-parameters are required only if there are user inputs while setting the post function.
Let us now have a look in detail.
FieldValidatorFactory
As mentioned earlier, this is the class to define the inputs and set the velocity context. In out case we have a single input 'field' which is the name of a custom field. Here is what the class look like:
private static final String FIELD_NAME = "field";
private static final String FIELDS = "fields";
private static final String NOT_DEFINED = "Not Defined";
private final CustomFieldManager customFieldManager;
public FieldValidatorFactory(CustomFieldManager customFieldManager) {
this.customFieldManager = customFieldManager;
}
@Override
protected void getVelocityParamsForEdit(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
velocityParams.put(FIELD_NAME, getFieldName(descriptor));
velocityParams.put(FIELDS, getCFFields());
}
@Override
protected void getVelocityParamsForInput(Map<String, Object> velocityParams) {
velocityParams.put(FIELDS, getCFFields());
}
@Override
protected void getVelocityParamsForView(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
velocityParams.put(FIELD_NAME, getFieldName(descriptor));
}
@SuppressWarnings("unchecked")
public Map<String, String> getDescriptorParams(Map<String, Object> conditionParams) {
if (conditionParams != null && conditionParams.containsKey(FIELD_NAME)) {
return EasyMap.build(FIELD_NAME, extractSingleParam(conditionParams, FIELD_NAME));
}
// Create a 'hard coded' parameter
return EasyMap.build();
}
private String getFieldName(AbstractDescriptor descriptor) {
//Extract field from the workflow
}
private Collection<CustomField> getCFFields() {
//Get list of custom fields
}
}
getFieldName(descriptor) is the method that retrieves the field from the descriptor and that is done as follows:
if (!(descriptor instanceof ValidatorDescriptor)) {
throw new IllegalArgumentException("Descriptor must be a ConditionDescriptor.");
}
ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor;
String field = (String) validatorDescriptor.getArgs().get(FIELD_NAME);
if (field != null && field.trim().length() > 0)
return field;
else
return NOT_DEFINED;
}
We have one more method getDescriptorParams in the class and this just return a map of sanitized parameters which will be passed into workflow plugin instances from the values in array form submitted by velocity. More info on that here.
getCFFields() does nothing but retrieves all the customfields:
return customFields;
Let us now move to the actual validator class. Here is how it looks like:
private final CustomFieldManager customFieldManager;
private static final String FIELD_NAME = "field";
public FieldValidator(CustomFieldManager customFieldManager) {
this.customFieldManager = customFieldManager;
}
public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException, WorkflowException {
Issue issue = (Issue) transientVars.get("issue");
String field = (String) args.get(FIELD_NAME);
CustomField customField = customFieldManager.getCustomFieldObjectByName(field);
if (customField!=null){
//Check if the custom field value is NULL
if (issue.getCustomFieldValue(customField) == null){
throw new InvalidInputException("The field:"+field+" is required!");
}
}
}
}
Note: Please note that some fields may not be configured for that issue and you can take it into account when you actually implement it!
Templates
Before We wrap up, let us quickly look at the velocity templates. We are using the same template 'edit-fieldValidator.vm' for both input-paramers and edit-parameters.
<td align="right" valign="top" bgcolor="#fffff0">
<span class="label">Custom Fields:</span>
</td>
<td bgcolor="#ffffff" nowrap>
<select name="field" id="field">
#foreach ($cf in $fields)
<option value="$cf.name"
#if ($cf.name.equals($field))
SELECTED
#end
>$cf.name</option>
#end
</select>
<br><font size="1">Select the Custom Field to be validated for NULL</font>
</td>
</tr>
view-fieldValidator.vm looks like this:
Field '$field' is Required.
#end
We now have our validator ready. Deploy the plugin into jira-home/plugins/installed-plugins (WEB-INF/lib if you created plugin-1 version), Create the Validator to check for a valid custom field in a valid workflow and test it!!
More details on workflow validator module can be found here! Njoy!!
Download the full source code at the end of the tutorial. And don't forget to post your comments/feedback!
Note: The tutorial is just to explain the basic concepts of Validators. Modify the buisness logic accordingly! And one tip on the custom fields - Custom field names are not unique and so it is advised to use the id for storing the the descriptor. You can anytime retrieve the name for displaying in the velocity etc.

field-not-empty-validator.zip |