Tracking states for each document is similar to doing manual workflow. If you don't have external requirements on querying states, I'd suggest dumping explicit state tracking on each document, and use the workflow to handle publishing documents. This allows you to add states and roles dynamically by simply adding task nodes in a "publish document" process flow.
As for integrating to struts, you need to implement a 'tasklist' page for a logged in user, and a method of completing these tasks. This would most likly be a task detail page of some type that would include 1 or more options (complete, reject, etc.). How the task gets to that page is not explicit in jbpm, and is currently something I've been thinking about. For your example, its pretty simple, so you might be able to get away with hard-coding the task detail page. If the *only* thing you can do is move the document to the next state, or roll it back, then you can have one screen. Just push it to your struts action url with the task id and go from there. If you need more than 1 type of task detail screen (as pretty much any real workflow system would eventually), you need to specify what url to forward to. Options for this include:
1) Simple "hacky" version: Just have your flow go to a generic screen, and 'override' that for specific tasks. Have an application level definition file that says "[flow].[task]=/somePath.do". This will give you a way to open a custom screen, but its a little hacky. You're also possibly going to run into workflow version issues later on.
2) Have a workflow screen mapping document in the par: Get and parse this file to map to different screens. "[task]=/somePath.do". This is somewhat better than above, but embeds application specific urls inside the workflow. Not that big of a deal in my opinion, but I'm sure others would disagree. Also, there are still some version issues. For example, if you change the screen '/somePath.do' for version 12 of the workflow, your testing might come out ok, but when somebody clicks that from version 9 and there's some piece of data missing (or whatever), you're in error town.
3) Have a custom task form parameter interface that specifies the app url to use. Same issues as #2, just shuffling the location of the definition. Personally, I'd like to be able to hard-code a variable in the task form parameter. Right now the default implementation allows you to specify which vars are passed, and their access (read|write) and rename them. I think we should also be able to pass a literal value to a task. There might be other uses, but I would use it in this instance to specify the task url.
4) Build some type of highly custom task form page rederer that would allow abstract form entry screen definitions, validation, and processing withing the par deploy unit, etc. This would be the "proper" way to do it, as it isolates the task screen details to the par, and takes care of superficial version problems. I really don't like this method for a number of reasons.
I've been doing work on the default webapp for jbpm recently. One of the things I added was the special variable processing ability in the task screen. The simplest method you could use, if you wanted something very easy and quick, would be to use a boolean var called 'approve'. In the startup event of each task node, set this var to 'false'. Then pass this var to the task. If the user completes the task they can specify the approve value.
The other option would simply be to have 2 transitions out of the task node. One to the next state, and one to the previous. Just name each transition something meaningful, like 'Approve' and 'Reject'. Then the user will click one or the other, and it should handle things accordingly.
Ok, I need to get back to my day job. Hope this helped a little.