Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
99 lines (74 sloc) 5.59 KB
layout title date comments permalink tags
post
JIRA workflows -Postfunction exceptions, database commits and Pretty workflow exception
2010-12-25
false
2010/12/jira-workflows-postfunction-exceptions.html
atlassian
jira
java

Hi again, For all of you who happens to be catholic - Happy Xmas futurama style.

So, back to coding again.

I assume most of you are familiar with JIRA post functions. So, would like to share my 0.02$ on the subject though. Boring stuff.

OS Workflow
The less known things about post functions are:
  • Workflow, steps and history are stored in different database tables than issue data. So Jira uses database commits to enforce data consistency.
  • Post functions might throw WorkflowException exception and workflow manager will rollback transaction in this case. Actually if the other exception will be thrown the transaction won't be commit (see Ofbiz Workflow et cetera), but imo it's implementation specific nuances and I wouldn't recommend that
  • In case you use MySQl - please ensure you have configured it properly - to use transactional database engine. If you haven't - let's say you do Resolve transition from status Opened -> to Closed and exception occurs. The workflow engine will think (no transaction rollback) the issue is closed, while the rest of the JIRA (issue view etc, issue.getStatus()) will think the issue is still Opened. Happened with me :).
The pain
I've found myself to put some conditioning into my custom wf functions, like if smth has ben selected in the transition screen view - the function should fail. The same could be achieved my using wf validators and for sure! would be more correct solution - it's the type of component that should do the validation thing. However - think about it - I have 1 thing to do during transition - it's one post function. And I have 3-8 validations for it, like is it configured properly, are all fields entered from the UI etc. It would be more flexible to do it using wf postfunction and 3-5 validations, but it's so much more work (at least configuration and maintenance). So I decided to stick with 1 postfunction approach.
The way to implement this would be to throw exceptionn during the wf transition. In the regular case I would throw smth like IllegalArgumentException, but for reasons mentioned earlier I decided to convert it to general WorkflowException. The thing with it is - in case it does contain exception cause (and I like to preserve stacktrace as long I can) - it messes the exception getMessage() result and user on-screen output is smth like "rootCause: Please specify ...", which is ... wrong.
So, the approach i decided to go with is:

//... postfunction class declaration {

public void execute(java.util.Map transientVars, java.util.Map args, com.opensymphony.module.propertyset.PropertySet ps) throws com.opensymphony.workflow.WorkflowException { try { //.. doStuff(); } catch(XYZIssueException e){ throw new WorkflowException(e); } //... catch(IllegalArgumentException e) { throw new PrettyMessageWorkflowException(e); } }

// ... rest the postfunction }

// And the pretty lady herself class PrettyMessageWorkflowException extends WorkflowException { // ------------------------------ FIELDS ------------------------------

private final String prettyMessage;

// --------------------------- CONSTRUCTORS ---------------------------

public PrettyMessageWorkflowException() { super(); this.prettyMessage = null; }

public PrettyMessageWorkflowException(String message) { super(message); this.prettyMessage = message; }

public PrettyMessageWorkflowException(Throwable rootCause) { super(rootCause); this.prettyMessage = null; }

public PrettyMessageWorkflowException(String message, Throwable rootCause) { super(message, rootCause); this.prettyMessage = message; }

// -------------------------- OTHER METHODS --------------------------

@Override public String getMessage() { if (this.prettyMessage == null || this.prettyMessage.isEmpty()) { return super.getMessage(); }

 return this.prettyMessage;

} }

It saves a day as usually :)