Sunday, April 06, 2008

Workflow Reloaded : Moving to OSWorkflow

I have been trying to model a workflow for one of my applications. I started with Bossa, but after getting stuck using parts of its API, I was well on my way to designing my own workflow engine using Bossa's Petri Net abstraction, until a colleague pointed out that I was trying to re-invent something for which solid, mature implementations already existed in the open-source world, and that, down the road, other developers would curse me for having stuck them with having to maintain this component.

This week, I describe my experience implementing a solution to my workflow problem using OSWorkflow, a popular and mature workflow engine developed by the OpenSymphony group. Actually, I was quite pleasantly surprised to see Hani Suleiman's name as the author of some of the classes. For those of you who don't know, he is also the author of the Bileblog. Although he doesn't write it anymore, its worth a read simply because of his ability to call bullshit on any number of things, and his ability to call a spade a...er...several very unmentionable things. I don't agree with a lot of the stuff he said, but it was a very entertaining (and sometimes informative) read nevertheless. Anyway, like I said, it was nice to see his name on the @author tags.

My initial take on OSWorkflow is that it is less "pure" in its abstraction, but probably closer to the way people think of a workflow in the real world. I show my workflow redrawn the OSWorkflow way below. Notice how similar it is to the picture of the back-of-the-envelope version of the workflow.

However, in trying to make OSWorkflow "user-friendly", the concept of Step (Place in Bossa) has been overloaded to include Action (Transition in Bossa). Because of this, there are extra constructs for Split and Join (indicated above by pink and blue), Initial Action to kick off a workflow, and Pre- and Post-Functions to "attach" to a Step (instead of being the input and output Transition to a Place as in Bossa). What's more, Splits have to be modeled as real Steps, while Joins don't. So for me there was some amount of unlearning to do. Another problem is that the documentation is rather sparse and a lot of what I did was by trial and error.

Also, my needs were slightly different from the standard usage. The workflow I was trying to build is a network of batch processes, which have dependencies on each other. The batch jobs will be run using JMS (more on that probably next week), so the application needs to be event-driven. That is, each of the Actions would call back the main Workflow Runner once it is done, so the runner can move the workflow forward.

I am using Spring for the rest of the application, so I planned on using Spring's event handling framework for this. OSWorkflow has built-in integration with Spring, which I used, but that does not expose the actual post-functions as Spring objects. Fortunately, I was able to find a solution in this springmodules page, which works even without having the osworkflow springmodules module in your classpath.

Configuration

Configuration is using Spring and a couple of XML files that are used to define the workflow to OSWorkflow. The Spring applicationContext.xml file is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

  <bean id="workflowStore" 
    class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>
  
  <bean id="workflowFactory" 
      class="com.opensymphony.workflow.spi.hibernate.SpringWorkflowFactory" 
      init-method="init">
    <property name="resource" value="workflow-defs.xml"/>
    <property name="reload" value="true"/>    
  </bean>

  <bean id="workflowConfiguration" 
      class="com.opensymphony.workflow.config.SpringConfiguration">
    <property name="factory" ref="workflowFactory"/>
    <property name="store" ref="workflowStore"/>
  </bean>
  
  <bean id="workflowTypeResolver" 
      class="com.opensymphony.workflow.util.SpringTypeResolver">
    <property name="functions">
      <map>
        <entry key="mockAction"><ref bean="mockAction"/></entry>
      </map>
    </property>
  </bean>

  <bean id="workflow" 
      class="com.opensymphony.workflow.basic.BasicWorkflow" scope="prototype">
    <constructor-arg><value>testuser</value></constructor-arg>
    <property name="configuration" ref="workflowConfiguration"/>
    <property name="resolver" ref="workflowTypeResolver"/>
  </bean>
    
  <bean id="mockAction" class="com.mycompany.myapp.workflow.MockAction" scope="prototype"/>
  
  <bean id="workflowRunner" 
      class="com.mycompany.myapp.workflow.WorkflowRunner" scope="prototype">
    <property name="workflow" ref="workflow"/>
  </bean>
</beans>

Here all except the last two beans are needed by OSWorkflow to define a Store and Factory, which in turn defines the configuration for the workflow. The workflowTypeResolver specifies that certain classes are going to be recognized as Spring functions by the Workflow. The mockAction is our Action class which we use for tests. In the real application, there will be specialized Action classes that will be injected into the appropriate workflow Steps.

The WorkflowFactory is built using the osworkflow file, workflow-defs.xml, which include a list of other workflow files, one for each workflow defined in the system. Both these files need to be in the application classpath.

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<workflows>
  <workflow name="workflow-def-1" type="resource" location="workflow-def-1.xml"/>
</workflows>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC 
  "-//OpenSymphony Group//DTD OSWorkflow 2.8//EN"
  "http://www.opensymphony.com/osworkflow/workflow_2_8.dtd">
<workflow>
  <initial-actions>
    <action id="0" name="start">
      <pre-functions>
        <function type="class">
          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
        </function>
        <function type="spring">
          <arg name="bean.name">mockAction</arg>
          <arg name="action.name">t0</arg>
        </function>
      </pre-functions>
      <results>
        <unconditional-result old-status="Finished" status="Queued" 
          owner="${caller}" step="1"/>
      </results>
    </action>
  </initial-actions>
  <steps>
    <step id="1" name="p1">
      <actions>
        <action id="1" name="t1">
          <results>
            <unconditional-result old-status="Finished" status="Queued" 
              owner="${caller}" step="26"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t1</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="26" name="s26">
      <actions>
        <action id="26" name="split26">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" split="26"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">split26</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="2" name="p2">
      <actions>
        <action id="2" name="t2">
          <results>
            <unconditional-result old-status="Finished" status="Queued" 
              owner="${caller}" step="34"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t2</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="34" name="s34">
      <actions>
        <action id="34" name="split34">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" split="34"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">split34</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="3" name="p3">
      <actions>
        <action id="3" name="t3">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" join="43"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t3</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="4" name="p4">
      <actions>
        <action id="4" name="t4">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" join="43"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t4</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="5" name="p5">
      <actions>
        <action id="5" name="t5">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" join="75"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t5</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="6" name="p6">
      <actions>
        <action id="6" name="t6">
          <results>
            <unconditional-result old-status="Finished" status="Queued" 
              owner="${caller}" step="7"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t6</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="7" name="p7">
      <actions>
        <action id="7" name="t7">
          <results>
            <unconditional-result old-status="Finished" owner="${caller}" join="75"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t7</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="8" name="p8">
      <actions>
        <action id="8" name="t8">
          <results>
            <unconditional-result old-status="Finished" status="Queued" 
              owner="${caller}" step="9"/>
          </results>
          <post-functions>
            <function type="spring">
              <arg name="bean.name">mockAction</arg>
              <arg name="action.name">t8</arg>
            </function>
          </post-functions>
        </action>
      </actions>
    </step>
    <step id="9" name="stop">
      <actions>
        <action id="9" name="t9" finish="true">
          <results>
            <unconditional-result old-status="Finished" 
              status="Complete" owner="${caller}"/>
          </results>
        </action>
      </actions>
    </step>
  </steps>
  <splits>
    <split id="26">
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="2"/>
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="6"/>
    </split>
    <split id="34">
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="3"/>
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="4"/>
    </split>
  </splits>
  <joins>
    <join id="43">
      <conditions type="AND">
        <condition type="beanshell">
          <arg name="script"><![CDATA[
            "Finished".equals(jn.getStep(3).getStatus()) &&
            "Finished".equals(jn.getStep(4).getStatus())
          ]]>
          </arg>
        </condition>
      </conditions>
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="5"/>
    </join>
    <join id="75">
      <conditions type="AND">
        <condition type="beanshell">
          <arg name="script"><![CDATA[
          "Finished".equals(jn.getStep(5).getStatus()) &&
          "Finished".equals(jn.getStep(7).getStatus())
          ]]>
          </arg>
        </condition>
      </conditions>
      <unconditional-result old-status="Finished" status="Queued" 
        owner="${caller}" step="8"/>
    </join>
  </joins>
</workflow>

The file above is the XML version of the workflow diagram above. It has approximately eight real steps, a start and stop step (which are pseudo steps in the sense that they don't do anything, but need to be modeled as regular steps here), two splits (each of which need to be modeled as steps) and two joins.

The reason the actual mockAction beans have been built as post-actions is because of the event based nature of the actions. Building them as pre-actions will result in an infinite loop because the workflow will never leave the first step. Since post-actions are called after the step is left, this is an ideal position to do this.

Event Subscriber and Publisher

The Event Subscriber is the WorkflowRunner class, which listens on events generated by the Action classes. The event contains the workflowId for the current Workflow instance, which is used to query the workflow to find the next available actions to execute. It implements Spring's ApplicationListener and exposes an onApplicationEvent which is triggered by events from the Actions. The code is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// WorkflowRunner.java
package com.mycompany.myapp.workflow;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import com.opensymphony.workflow.Workflow;
import com.opensymphony.workflow.WorkflowException;
import com.opensymphony.workflow.spi.Step;

public class WorkflowRunner implements ApplicationListener {

  private final Log log = LogFactory.getLog(getClass());
  
  private String workflowName;
  private Workflow workflow;
  private Map<String,Object> inputs;
  
  private Set<Integer> alreadyExecuted = new HashSet<Integer>();
  
  @Required
  public void setWorkflow(Workflow workflow) {
    this.workflow = workflow;
  }
  
  public void setWorkflowName(String workflowName) {
    this.workflowName = workflowName;
  }
  
  public void setInputs(Map<String,Object> inputs) {
    this.inputs = inputs;
  }
  
  public long init(int initialActionId) throws Exception {
    long workflowId = workflow.initialize(workflowName, initialActionId, inputs);
    return workflowId;
  }
  
  @SuppressWarnings({"unused","unchecked"})
  public void process(long workflowId) {
    List<Step> currentSteps = workflow.getCurrentSteps(workflowId);
    for (Step currentStep : currentSteps) {
      int[] availableActions = workflow.getAvailableActions(workflowId, inputs);
      for (int availableAction : availableActions) {
        if (alreadyExecuted.contains(availableAction)) {
          continue;
        }
        try {
          System.out.println("Executing action.id=" + availableAction);
          workflow.doAction(workflowId, availableAction, inputs);
          alreadyExecuted.add(availableAction);
        } catch (WorkflowException e) {
          log.error("Exception in (workflow,action.id)=(" + workflowName + "," + 
            availableAction + "). Workflow stopped", e);
        }
      }
    }
  }
  
  public void onApplicationEvent(ApplicationEvent event) {
    if (event.getSource() instanceof Long) {
      process((Long) event.getSource());
    }
  }
}

The events are published into the ApplicationContext from our Action classes. The MockAction is only an example which prints the name of the action being executed. It implements FunctionProvider (so it can be called from within OSWorkflow as a function) and ApplicationContextAware (so it can publish events into Spring's ApplicationContext).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// MockAction.java
package com.mycompany.myapp.workflow;

import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;

import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.FunctionProvider;
import com.opensymphony.workflow.WorkflowException;
import com.opensymphony.workflow.spi.WorkflowEntry;

public class MockAction implements FunctionProvider, ApplicationContextAware {

  private final Log log = LogFactory.getLog(getClass());
  
  private ApplicationContext applicationContext;

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  /**
   * @see com.opensymphony.workflow.FunctionProvider#execute(
   *   java.util.Map, java.util.Map, com.opensymphony.module.propertyset.PropertySet)
   */
  @SuppressWarnings({"serial","unchecked"})
  public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException {
    String actionName = (String) args.get("action.name");
    WorkflowEntry workflowEntry = (WorkflowEntry) transientVars.get("entry");
    long workflowId = workflowEntry.getId();
    applicationContext.publishEvent(new ApplicationEvent(workflowId) {});
  }
}

Example Client Code

Finally, here is how I plan to call the workflow. The workflow is kickstarted by a user request, however, any further movement in the workflow is done by the events, which call the WorkflowRunner.process method internally. Here is the JUnit test I used to test this out.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// WorkflowRunnerTest.java
package com.mycompany.myapp.workflow;

import java.util.HashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.opensymphony.workflow.Workflow;

public class WorkflowRunnerTest {

  private WorkflowRunner runner;
  private Map<String,Object> inputs = new HashMap<String,Object>();
  private long workflowId;
  
  @Before
  public void setUp() throws Exception {
    ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {
      "classpath:applicationContext.xml"
    }); 
    runner = (WorkflowRunner) context.getBean("workflowRunner");
    runner.setWorkflowName("workflow-def-1");
    runner.setInputs(inputs);
    workflowId = runner.init(0);
  }

  @Test
  public void testRunner() throws Exception {
    runner.process(workflowId);
  }
}

The output looks like this, which seems reasonable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Executing action.id=1
Executing action.id=26
Executing action.id=2
Executing action.id=6
Executing action.id=34
Executing action.id=7
Executing action.id=3
Executing action.id=4
Executing action.id=5
Executing action.id=8
Executing action.id=9

Conclusion

OSWorkflow seems to have quite a steep learning curve, but it is quite powerful once you understand how it all works. I don't know everything about it (not even close), and I am probably only going to explore more unless I have to. Like any other workflow engine, the hardest work is to design your workflow. During my searches for things OSWorkflow related, I did come across OSWorkflow: A guide for Java developers and architects, a book by Diego Naya, who also seems to be quite active on the OSWorkflow mailing list. I haven't read it, but it may be worth buying at some point in the future. OSWorkflow also comes with a GUI designer, which I did try in desperation at one point, but I could not do much with it, so I cannot comment on how good it is.

18 comments (moderated to prevent spam):

Rogelio said...

Great!

I've been trying to find a partner to tackle this big boy, OsWorkflow in particular and the whole Workflow/BPM scenario in general.

I'm going to take your post as a starting point. In the past I've had some failed attempts, and post my questions/progress in the messages section.

I have the need of internal and external workflows, an internal workflow to me is something inside an inhouse application when some intracompany actors work together for a common goal and the application lives in behind the firewall, but an external one is something that implies your company and other companies working together in one large multi-step workflow exchanging processing and each one following the part of the workflow that they own alsoe with a common goal, kind of B2B processing.


Well, I'm going to be very interested in the next post.

Sujit Pal said...

Hi Rogelio, thanks for your comments. My interest in OSWorkflow (or workflow in general) is transitory, once I tackle this problem I will probably move on to other things. However, last week I was able to hook up the current setup with ApacheMQ which I describe in my next post. Hopefully it will live up to your expectations :-).

Your setup looks quite interesting btw. Executing a workflow across corporate boundaries seems pretty ambitious. Good luck with the project!

Rogelio said...

Yup, I'm also interested in ActiveMQ, we have IBM MQ Series but I prefer a lighter MQ engine that has a Spring interface.

At some steps in my workflow, I'd need to run a native binary application, this app. is a massive file processing that spawns several copies of itself, is not frequent but sometimes it aborts because lack disk space, when that happens we need to release some disk space and re-run this step again.

For this step I'd need to do something like java.lang.Runtime.exec(), How can I do that with OSW? and If the binary app. aborts, I'd get an appropriate return value, that will be stored by OSW as the result of this step and then wait for an operator using a web app that inspects this workflow to run the step again or just take notice that step finished correctly, I assume that this is perfectly possible by OSW.

Thanks.

Rogelio

Sujit Pal said...

To do it directly without the additional decoupling I describe in my next post, you would wrap the Runtime.exec() call within a FunctionProvider implementation, similar to what I do here for MockAction and JmsAction in the next post. However, my preference would be to hand it off to JMS to run asynchronously, and to callback on completion. In the JmsAction, I trap the exception and just log it, but you could make it send an email, and have the operator fix whatever is wrong and resubmit. Once the job completes, it will send a callback to the workflow and things will go on as normal.

Kunal said...

Hi Sujit,

I tried to used the code posted by you. When I run the application its showing some errors.
My statck trace is :

2009-01-02 11:45:49 StandardContext[/OSWorkTry]Loading Spring root WebApplicationContext
2009-01-02 11:45:51 StandardContext[/OSWorkTry]Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowRunner' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflow' while setting property 'workflow'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflow' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflowConfiguration' while setting property 'configuration'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowConfiguration' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflowFactory' while setting property 'factory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowFactory' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflow' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflowConfiguration' while setting property 'configuration'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowConfiguration' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflowFactory' while setting property 'factory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowFactory' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowConfiguration' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Can't resolve reference to bean 'workflowFactory' while setting property 'factory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowFactory' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowFactory' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
at com.opensymphony.workflow.spi.hibernate.SpringWorkflowFactory.init(SpringWorkflowFactory.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1093)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1063)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:363)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:176)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:105)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1013)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:824)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:345)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:176)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:105)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1013)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:824)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:345)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:176)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:105)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1013)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:824)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:345)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:203)
at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:614)
at org.springframework.context.support.AbstractApplicationContext.registerListeners(AbstractApplicationContext.java:496)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:317)
at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:134)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:246)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:184)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3827)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4343)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:823)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:807)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:595)
at org.apache.catalina.core.StandardHostDeployer.install(StandardHostDeployer.java:277)
at org.apache.catalina.core.StandardHost.install(StandardHost.java:832)
at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:701)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:432)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:983)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:349)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1091)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:789)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1083)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:478)
at org.apache.catalina.core.StandardService.start(StandardService.java:480)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:2313)
at org.apache.catalina.startup.Catalina.start(Catalina.java:556)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:287)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:425)
Caused by: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection refused: connect
at com.opensymphony.workflow.loader.XMLWorkflowFactory.initDone(XMLWorkflowFactory.java:130)
at com.opensymphony.workflow.spi.hibernate.SpringWorkflowFactory.init(SpringWorkflowFactory.java:44)
... 63 more
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.Socket.connect(Socket.java:507)
at java.net.Socket.connect(Socket.java:457)
at sun.net.NetworkClient.doConnect(NetworkClient.java:157)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:365)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:477)
at sun.net.www.http.HttpClient.init(HttpClient.java:214)
at sun.net.www.http.HttpClient.New(HttpClient.java:287)
at sun.net.www.http.HttpClient.New(HttpClient.java:299)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:792)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:744)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:669)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:913)
at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
at org.apache.xerces.impl.XMLEntityManager.startEntity(Unknown Source)
at org.apache.xerces.impl.XMLEntityManager.startDTDEntity(Unknown Source)
at org.apache.xerces.impl.XMLDTDScannerImpl.setInputSource(Unknown Source)
at org.apache.xerces.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
at javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
at com.opensymphony.workflow.loader.XMLWorkflowFactory.initDone(XMLWorkflowFactory.java:115)
... 64 more

2009-01-02 11:45:51 StandardContext[/OSWorkTry]Closing Spring root WebApplicationContext
2009-01-02 11:45:51 StandardContext[/servlets-examples]ContextListener: contextInitialized()
2009-01-02 11:45:51 StandardContext[/servlets-examples]SessionListener: contextInitialized()
2009-01-02 11:45:52 StandardContext[/WizdomTree2.1]Loading Spring root WebApplicationContext
2009-01-02 11:46:10 StandardContext[/WizdomTree2.1]Set web app root system property: 'WizdomTree2.1' = [D:\Program Files\SDE3.0\Tomcat 5.0\webapps\WizdomTree2.1\]
2009-01-02 11:46:10 StandardContext[/WizdomTree2.1]Initializing Log4J from [D:\Program Files\SDE3.0\Tomcat 5.0\webapps\WizdomTree2.1\WEB-INF\log4j.properties]
2009-01-02 11:46:24 StandardContext[/WizdomTree2.1]Initializing WebApplicationContext for Struts ActionServlet 'action', module ''
2009-01-02 11:46:27 StandardContext[/WT]Loading Spring root WebApplicationContext
2009-01-02 11:46:42 StandardContext[/WT]Set web app root system property: 'WizdomTree2.1_I2' = [D:\Program Files\SDE3.0\Tomcat 5.0\webapps\WT\]
2009-01-02 11:46:42 StandardContext[/WT]Initializing Log4J from [D:\Program Files\SDE3.0\Tomcat 5.0\webapps\WT\WEB-INF\log4j.properties]
2009-01-02 11:47:01 StandardContext[/WT]Initializing WebApplicationContext for Struts ActionServlet 'action', module ''

Sujit Pal said...

Hi Kunal, from the stacktrace, it appears as though your workflowFactory is not starting up possibly because of badly formatted xml in your workflow-defs.xml.

Anonymous said...

I get a connection time out error when I try to run the work flow given above.

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'workflowFactory' defined in class path resource [workflow.xml]: Invocation of init method failed; nested exception is java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection timed out: connect
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1302)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:463)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:404)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:375)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:263)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:170)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:260)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:184)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:163)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:430)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:729)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:381)
at org.springframework.context.support.ClassPathXmlApplicationContext.(init)(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.(init)(ClassPathXmlApplicationContext.java:93)
at com.catmktg.flexconn.workflow.functions.WorkflowRunnerTest.main(WorkflowRunnerTest.java:41)
Caused by: java.lang.RuntimeException: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection timed out: connect
at com.opensymphony.workflow.spi.hibernate.SpringWorkflowFactory.init(SpringWorkflowFactory.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1378)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1339)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1299)
... 15 more
Caused by: com.opensymphony.workflow.InvalidWorkflowDescriptorException: Error in workflow config: root cause: Connection timed out: connect
at com.opensymphony.workflow.loader.XMLWorkflowFactory.initDone(XMLWorkflowFactory.java:130)
at com.opensymphony.workflow.spi.hibernate.SpringWorkflowFactory.init(SpringWorkflowFactory.java:44)
... 22 more
Caused by: java.net.ConnectException: Connection timed out: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.Socket.connect(Socket.java:516)
at java.net.Socket.connect(Socket.java:466)
at sun.net.NetworkClient.doConnect(NetworkClient.java:157)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:365)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:477)
at sun.net.www.http.HttpClient.(init)(HttpClient.java:214)
at sun.net.www.http.HttpClient.New(HttpClient.java:287)
at sun.net.www.http.HttpClient.New(HttpClient.java:299)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:795)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:747)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:672)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:916)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:973)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:905)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.java:872)
at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.java:282)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(XMLDocumentScannerImpl.java:1021)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:368)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:834)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:148)
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:250)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:292)
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:98)
at com.opensymphony.workflow.loader.XMLWorkflowFactory.initDone(XMLWorkflowFactory.java:115)
... 23 more

My guess is that it has some problem while calling the factory method init of SpringWorkFlowFactory, probably because of a wrong workflow.

Sujit Pal said...

Hi, all I can say is that it worked for me. There seems to be a connect timeout exception in the stack trace, and I don't think that I have any components in here that raise that -- I do see a HttpClient so perhaps that may be the cause?

Mridul said...

Whats the future of OSWorkflow,I was planning to evaluate,however I see that there are no new release since last 3 years (only v2.8 that too Jan 2006).

I just saw GUI designer, its swing based and absolutely nothing. defining WF and changing WF is the main activity and in today's world developer just hate to write .xml files by hand and the customers wants solution which they may change themselves.

Do u any suggestion for good workflow engine which can also be used as BPM engine and supports industry standards.

Sujit Pal said...

Hi Mridul, thanks for the comment -- unfortunately I have no idea of the timetable for the OSWorkflow project. It is quite popular from the looks of it, but perhaps development has stopped because its now good enough? Also, my approach to OSS is that if I like a part of some project, I will use it, and if I need components to help me (such as your requirement for a GUI XML configuration builder), I generally build it (and contribute it back) since I have the source available. However, I understand from an evaluation standpoint, you want to select a product that matches up with your requirements the closest. The only other workflow tool/library I am aware of is Bossa, and that has less user-level support than OSWorkflow AFAIK, but you may want to check it out.

Ozar said...

Hi, I'm trying to find a way to form a loop in OSWorkflow, but couldn't figure out a way how to achieve it.

My use case, requires that at any stage of an entity denying validation in the process, the flow should go backwards or directly to the beginning.

Any ideas?

Sujit Pal said...

Hi Ozar, wouldn't it be possible to just use a fork on a condition, and have one of the paths go back to the previous state and the other to go forward?

Ozar said...

Sujit, great tip. However when I really attempted to really use OSWorkflow and defined my flows in the XML, as soon as I want to use the data persistence layer, a get a null pointer exception.

That is, the workflow seems to run nice in memory, but when I change the config to use the database, a get a null pointer excepiton (I'm sure the datasource and db setup is OK).

I'm wondering if I need to populate the database with some initial data first (but then how - there's no example in the docs). Noone from OopnSymphony answered me.

Any ideas?

Sujit Pal said...

Thanks Ozar, and sorry, but I haven't used the database persistence with OSWorkflow - the workflow I model here is about as complicated as my workflows get, so never needed database persistence.

Anonymous said...

Hello Mr. Sujit, I am not able to implement splits and joins in my application via workflow descriptor. Do you have any document explaining this particular issue.
And fyi my application is using wicket.

Thanks in advance.

Sujit Pal said...

Hi, no I don't have any documentation beyond whats in the post, sorry.

tubingen said...

is it possible to do multi split I mean , I dont know know how many splits I want to make till runtime, In my case I have review comitee and based on number of officials in the comitee, I want to split task to all comitee members, can I do this using split and join ?Please advice.

Sujit Pal said...

Hi tubingen, I am not 100% sure whether dynamic sized splits are possible because I have never used it, but this InfoQ article describing the OSWorkflow Java API seems to indicates that it should be possible.