写的很详细,收藏的!java
-------------------------------------------------------web
接口选择:
osworkflow提供几种实现com.opensymphony.workflow.Workflow接口的类。
BasicWorkflow:
不提供事务支持,你能够经过持久层来实现事务处理。
Workflow wf = new BasicWorkflow(username)
这里的username是用来关联当前请求的用户。
EJBWorkflow:
用ejb容器来管理事务。在ejb-jar.xml中进行配置。
Workflow wf = new EJBWorkflow()
这里没有必要想basicworkflow和ofbizworkflow那样给出username。由于ejb容器已经校验过的。
Ofbizworkflow:
与basicworkflow比较类似,不一样只在于须要事务支持的方法由ofbiz TransactionUtil calls来包装。
建立新的工做流实例:
这里是以basicworkflow为例子
sql
执行action:
shell
查询:
值得注意的是:并非全部的 workflow stores支持查询。当前的hibernate,jdbc和内存工做流存储支持查询。Hibernate存储不支持mixed-type查询(如,一个查询使用到了历史和当前step contexts)。为了执行一个查询,须要构造出一个WorkflowExpressionQuery对象。查询方法是在这个对象上被调用的。
简单查询、嵌套查询、mixed-context查询(不支持hibernate工做流存储)在docs文档的5.4部分都有。
Step
大体至关于流程所在的位置。譬如企业年检,年检报告书在企业端算一个step,在工商局算第二个step,在复核窗口算第三个step。每一个step能够有多种状态(status)和多个动做(action),用Workflow.getCurrentSteps()能够得到全部当前的step(若是有并列流程,则可能同时有多个step,例如一次年检可能同时位于“初审”step和“广告经营资格审查”step)。
Status
流程在某个step中的状态。很容易理解,譬如“待认领”、“审核不经过”之类的。OSWorkflow中的状态彻底是由开发者自定义的,状态判别纯粹是字符串比对,灵活性至关强,并且能够把定义文件作得很好看。
Action
致使流程状态变迁的动做。一个action典型地由两部分组成:能够执行此动做的条件(conditions),以及执行此动做的结果(results)。条件能够用BeanShell脚原本判断,所以具备很大的灵活性,几乎任何与流程相关的东西均可以用来作判断。
Result
执行动做后的结果。这是个比较重要的概念。result分为两种,conditional-result和unconditional-result。执行一个动做以后,首先判断全部conditional-result的条件是否知足,知足则使用该结果;若是没有任何contidional-result知足条件,则使用unconditional-result。unconditional-result须要指定两部分信息:old-status,表示“当前step的状态变成什么”;后续状态,多是用step+status指定一个新状态,也可能进入split或者join。
conditional-result很是有用。仍是以年检为例,一样是提交年检报告书,“未提交”和“被退回”是不一样的状态,在这两个状态基础上执行“提交”动做,结果分别是“初次提交”和“退回以后再次提交”。这时能够考虑在“提交”动做上用conditional-result。
Split/Join
流程的切分和融合。很简单的概念,split提供多个result;join则判断多个current step的状态,提供一个result。
* * *
熟悉这些概念,在流程定义中尽可能使用中文,能够给业务代码和表现层带来不少方便。数据库
目的
这篇指导资料的目的是介绍OSWorkflow的全部概念,指导你如何使用它,而且保证你逐步理解OSWorkflow的关键内容。apache
本指导资料假定你已经部署OSWorkflow的范例应用在你的container上。范例应用部署是使用基于内存的数据存储,这样你不须要担忧如何配置其余持久化的例子。范例应用的目的是为了说明如何应用OSWorkflow,一旦你精通了OSWorkflow的流程定义描述符概念和要素,应该能经过阅读这些流程定义文件而了解实际的流程。设计模式
本指导资料目前有3部分:
1. 你的第一个工做流
2. 测试你的工做流
3. 更多的流程定义描述符概念数组
1. Your first workflow
建立描述符
首先,让咱们来定义工做流。你可使用任何名字来命名工做流。一个工做流对应一个XML格式的定义文件。让咱们来开始新建一个“myworkflow.xml”的文件,这是样板文件:tomcat
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC
"-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
...
</initial-actions>
<steps>
...
</steps>
</workflow>
首先是标准的XML头部,要注意的是OSWorkflow将会经过这些指定的DTD来验证XML内容的合法性。你可使用绝大多数的XML编辑工具来编辑它,而且能够highlight相应的错误。安全
步骤和动做
接下来咱们来定义初始化动做和步骤。首先须要理解的OSWorkflow重要概念是steps (步骤) 和 actions (动做)。一个步骤是工做流所处的位置,好比一个简单的工做流过程,它可能从一个步骤流转到另一个步骤(或者有时候仍是停留在同样的步骤)。举例来讲,一个文档管理系统的流程,它的步骤名称可能有“First Draft - 草案初稿”,“Edit Stage -编辑阶段”,“At publisher - 出版商”等。
动做指定了可能发生在步骤内的转变,一般会致使步骤的变动。在咱们的文件管理系统中,在“草案初稿”这个步骤可能有“start first draft - 开始草案初稿”和“complete first draft - 完成草案初稿”这样2个动做。
简单的说,步骤是“在哪里”,动做是“能够去哪里”。
初始化步骤是一种特殊类型的步骤,它用来启动工做流。在一个工做流程开始前,它是没有状态,不处在任何一个步骤,用户必须采起某些动做才能开始这个流程。这些特殊步骤被定义在 <initial-actions>。
在咱们的例子里面,假定只有一个简单的初始化步骤:“Start Workflow”,它的定义在里面
<initial-actions>:
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished" status="Queued" step="1"/>
</results>
</action>
这个动做是最简单的类型,只是简单地指明了下一个咱们要去的步骤和状态。
工做流状态
工做流状态是一个用来描述工做流程中具体步骤状态的字符串。在咱们的文档管理系统中,在“草案初稿”这个步骤可能有2个不一样的状态:“Underway - 进行中”和“Queued - 等候处理中”
咱们使用“Queued”指明这个条目已经被排入“First Draft”步骤的队列。好比说某人请求编写某篇文档,可是尚未指定做者,那么这个文档在“First Draft”步骤的状态就是“Queued”。“Underway”状态被用来指明一个做者已经挑选了一篇文档开始撰写,并且可能正在锁定这篇文档。
第一个步骤
让咱们来看第一个步骤是怎样被定义在<steps>元素中的。咱们有2个动做:第一个动做是保持当前步骤不变,只是改变了状态到“Underway”,第二个动做是移动到工做流的下一步骤。咱们来添加以下的内容到<steps>元素:
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<results>
<unconditional-result old-status="Finished"
status="Underway" step="1"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<results>
<unconditional-result old-status="Finished"
status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />这样咱们就定义了2个动做,old-status属性是用来指明当前步骤完成之后的状态是什么,在大多数的应用中,一般用"Finished"表示。
上面定义的这2个动做是没有任何限制的。好比,一个用户能够调用action 2而不用先调用action 1。很明显的,咱们若是没有开始撰写草稿,是不可能去完成一个草稿的。一样的,上面的定义也容许你开始撰写草稿屡次,这也是毫无心义的。咱们也没有作任何的处理去限制其余用户完成别人的草稿。这些都应该须要想办法避免。
让咱们来一次解决这些问题。首先,咱们须要指定只有工做流的状态为“Queued”的时候,一个caller (调用者)才能开始撰写草稿的动做。这样就能够阻止其余用户屡次调用开始撰写草稿的动做。咱们须要指定动做的约束,约束是由Condition(条件)组成。
条件
OSWorkflow 有不少有用的内置条件可使用。在此相关的条件是“StatusCondition - 状态条件”。 条件也能够接受参数,参数的名称一般被定义在javadocs里(若是是使用Java Class实现的条件的话)。在这个例子里面,状态条件接受一个名为“status”的参数,指明了须要检查的状态条件。咱们能够从下面的xml定义里面清楚的理解:
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Underway" step="1"/>
</results>
</action>但愿对于条件的理解如今已经清楚了。上面的条件定义保证了动做1只能在当前状态为“Queued”的时候才能被调用,也就是说在初始化动做被调用之后。
函数
接下来,咱们想在一个用户开始撰写草稿之后,设置他为“owner”。为了达到这样的目的,咱们须要作2件事情:
1) 经过一个函数设置“caller”变量在当前的环境设置里。
2) 根据“caller”变量来设置“owner”属性。
函数是OSWorkflow的一个功能强大的特性。函数基本上是一个在工做流程中的工做单位,他不会影响到流程自己。举例来讲,你可能有一个“SendEmail”的函数,用来在某些特定的流程流转发生时来发送email提醒。
函数也能够用来添加变量到当前的环境设置里。变量是一个指定名称的对象,能够用来在工做流中被之后的函数或者脚本使用。
OSWorkflow提供了一些内置的经常使用函数。其中一个称为“Caller”,这个函数会得到当前调用工做流的用户,并把它放入一个名为“caller”的字符型变量中。
由于咱们须要追踪是哪一个用户开始了编写草稿,因此可使用这个函数来修改咱们的动做定义:
<action id="1" name="Start First Draft">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"status="Underway"
step="1" owner="${caller}"/>
</results>
</action>h3 组合起来
把这些概念都组合起来,这样咱们就有了动做1:
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"status="Underway"
step="1" owner="${caller}"/>
</results>
</action>咱们使用相似想法来设置动做2:
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished" status="Queued" step="2"/>
</results>
</action>在这里咱们指定了一个新的条件:“allow owner only”。这样可以保证只有开始撰写这份草稿的用户才能完成它。而状态条件确保了只有在“Underway”状态下的流程才能调用“finish first draft”动做。
把他们组合在一块儿,咱们就有了第一个流程定义:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC
"-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished"
status="Queued" step="1"/>
</results>
</action>
</initial-actions>
<steps>
<step id="1" name="First Draft">
<actions>
<action id="1" name="Start First Draft">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Queued</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished" status="Underway"
step="1" owner="${caller}"/>
</results>
</action>
<action id="2" name="Finish First Draft">
<restrict-to>
<conditions type="AND">
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.
AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<results>
<unconditional-result old-status="Finished"
status="Queued" step="2"/>
</results>
</action>
</actions>
</step>
<step id="2" name="finished" />
</steps>
</workflow>如今这个工做流的定义已经完整了,让咱们来测试和检查它的运行。
2. Testing your workflow
如今咱们已经完成了一个完整的工做流定义,下一步是检验它是否按照咱们预想的方式执行。
在一个快速开发环境中,最简单的方法就是写一个测试案例。经过测试案例调用工做流,根据校验结果和捕捉可能发生的错误,来保证流程定义的正确性。
咱们假设你已经熟悉Junit和了解怎样编写测试案例。若是你对这些知识还不了解的话,能够去JUnit的网站查找、阅读相关文档。编写测试案例会成为你的一个很是有用的工具。
在开始载入流程定义、调用动做之前,咱们须要配置OSWorkflow的数据存储方式和定义文件的位置等。
配置 osworkflow.xml
咱们须要建立的第一个文件是 osworkflow.xml。子:
<osworkflow>
<persistence class="com.opensymphony.workflow.
spi.memory.MemoryWorkflowStore"/>
<factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
<property key="resource" value="workflows.xml" />
</factory>
</osworkflow>这个例子指明了咱们准备使用内存 (MemoryWorkflowStore) 来保存流程数据。这样能够减小设置数据库的相关信息,减小出问题的可能性。用内存持久化对于测试来讲是很是方便的。
Workflow factories
上面的配置文件还指明了咱们工做流工厂(XMLWorkflowFactory),工做流工厂的主要功能是管理流程定义文件,包括读取定义文件和修改定义文件的功能。经过'resource'这个属性指明了采用经过从classpath中读取流程定义文件的方式,按照这个定义,接下来咱们须要在classpath中建立一个名为workflows.xml的文件。
workflows.xml 的内容:
<workflows>
<workflow name="mytest" type="resource" location="myworkflow.xml"/>
</workflows>咱们把 myworkflow.xml 和workflows.xml放在同一目录,这样它就可以被工做流工厂读取了。
这样就完成了配置,接下来是初始化一个流程并调用它。
Initialising OSWorkflow
OSWorkflow 的调用模式至关简单:经过一个主要的接口来执行大部分操做。这个接口就是 Workflow interface,及其扩展 AbstractWorkflow 的实现,例如EJBWorkflow 和 SOAPWorkflow. 为了简单起见,咱们使用最基本的一种: BasicWorkflow。
首先,咱们来建立Workflow。在实际项目中,这个对象应该被放在一个全局的位置上以供重用,由于每次都建立一个新的Workflow对象是须要耗费比较昂贵的系统资源。在这里的例子,咱们采用BasicWorkflow,它的构建器由一个当前调用者的用户名构成,固然咱们不多看到单用户的工做流应用,能够参考其余的Workflow实现有不一样的方式去得到当前调用者。
为了简单起见,咱们采用BasicWorkflow来建立一个单一的用户模式,避免编写其余获取用户方法的麻烦。
这样咱们来建立一个'testuser'调用的workflow:
Workflow workflow = new BasicWorkflow("testuser";下一步是提供配置文件,在大多数状况下,只是简单的传递一个DefaultConfiguration实例:
DefaultConfiguration config = new DefaultConfiguration();
workflow.setConfiguration(config);如今咱们已经建立而且配置好了一个workflow,接下来就是开始调用它了。
启动和进行一个工做流程
首先咱们须要调用initialize 方法来启动一个工做流程,这个方法有3个参数,workflow name (定义在workflows.xml里,经过workflow factory处理), action ID (咱们要调用的初始化动做的ID),和初始化变量。 由于在例子里面不需初始化变量,因此咱们只是传递一个null,
long workflowId = workflow.initialize("mytest", 1, null);咱们如今已经有了一个工做流实例,返回的workflowId能够在后续的操做中来表明这个实例。这个参数会在Workflow interface的绝大部分方法中用到。
检验工做流
如今让咱们来检验启动的工做流实例是否按照咱们所预期的那样运行。根据流程定义,咱们指望的当前步骤是第一步,并且应该能够执行第一个动做(开始编写草稿)。
Collection currentSteps = workflow.getCurrentSteps
(workflowId);
//校验只有一个当前步骤
assertEquals("Unexpected number of current steps",
1, currentSteps.size());
//校验这个步骤是1
Step currentStep = (Step)currentSteps.iterator().next();
assertEquals("Unexpected current step", 1,
currentStep.getStepId());
int[] availableActions =
workflow.getAvailableActions(workflowId);
//校验只有一个可执行的动做
assertEquals("Unexpected number of available actions", 1,
availableActions.length);
//校验这个步骤是1
assertEquals("Unexpected available action", 1, availableActions[0]);执行动做
如今已经校验完了工做流实例正如咱们所指望的到了第一个步骤,让咱们来开始执行第一个动做:
workflow.doAction(workflowId, 1, null);这是简单的调用第一个动做,工做流引擎根据指定的条件,改变状态到‘Underway’,而且保持在当前步骤。
如今咱们能够相似地调用第2个动做,它所须要的条件已经都知足了。
在调用完第2个动做之后,根据流程定义就没有可用的动做了,getAvailableActions将会返回一个空数组。
Congratulations, 你已经完成了一个工做流定义而且成功地调用了它。下一节咱们将会讲解osworkflow一些更深刻的概念。
3. Further descriptor concepts
定义条件和函数
你也许已经注意到,到目前为止,咱们定义的条件和函数类型都是“class”。这种类型的条件和函数接受一个参数:“class.name”,以此来指明一个实现FunctionProvider或Condition接口的完整类名。
在osworkflow里面也有一些其余内置的类型,包括beanshell,无状态的session bean,JNDI树上的函数等。咱们在下面的例子里使用beanshell类型。
Property sets
咱们可能须要在工做流的任意步骤持久化一些少许数据。在osworkflow里,这是经过OpenSymphony的PropertySet library来实现。一个PropertySet基本上是一个能够持久化的类型安全map,你能够添加任意的数据到propertyset(一个工做流实例对应一个propertyset),并在之后的流程中再读取这些数据。除非你特别指定操做,不然propertyset中的数据不会被清空或者被删除。任意的函数和条件均可以和propertyset交互,以beanshell script来讲,能够在脚本上下文中用“propertyset”这个名字来获取。下面来看具体写法是怎么样的,让咱们增长以下的代码在“Start First Draft”动做的pre-functions里面:
<function type="beanshell">
<arg name="script">propertySet.setString("foo", "bar"</arg>
</function>这样咱们就添加了一个持久化的属性“foo”,它的值是“bar”。这样在之后的流程中,咱们就能够得到这个值了。
Transient Map 临时变量
另一个和propertyset变量相对的概念是临时变量:“transientVars”。临时变量是一个简单的map,只是在当前的工做流调用的上下文内有效。它包括当前的工做流实例,工做流定义等对应值的引用。你能够经过FunctionProvider的javadoc来查看这个map有那些可用的key。
还记得咱们在教程的第2部分传入的那个null吗?若是咱们不传入null的话,那么这些输入数据将会被添加到临时变量的map里。
inputs 输入
每次调用workflow的动做时能够输入一个可选的map,能够在这个map里面包含供函数和条件使用的任何数据,它不会被持久化,只是一个简单的数据传递。
Validators 校验器
为了让工做流可以校验输入的数据,引入了校验器的概念。一个校验器和函数,条件的实现方式很是相似(好比,它能够是一个class,脚本,或者EJB)。在这个教程里面,咱们将会定义一个校验器,在“finish first draft”这个步骤,校验用户输入的数据“working.title”不能超过30个字符。这个校验器看起来是这样的:
package com.mycompany.validators;
public class TitleValidator implements Validator
{
public void validate(Map transientVars, Map args,
PropertySet ps)
throws InvalidInputException, WorkflowException
{
String title =
(String)transientVars.get("working.title";
if(title == null)
throw new InvalidInputException("Missing working.title";
if(title.length() > 30)
throw new InvalidInputException("Working title too long";
}
}而后经过在流程定义文件添加validators元素,就能够登记这个校验器了:
<validators>
<validator type="class">
<arg name="class.name">
com.mycompany.validators.TitleValidator
</arg>
</validator>
</validators>这样,当咱们执行动做2的时候,这个校验器将会被调用,而且检验咱们的输入。这样在测试代码里面,若是加上:
Map inputs = new HashMap();
inputs.put("working.title",
"the quick brown fox jumped over the lazy dog," +
" thus making this a very long title";
workflow.doAction(workflowId, 2, inputs);咱们将会获得一个InvalidInputException,这个动做将不会被执行。减小输入的title字符,将会让这个动做成功执行。
咱们已经介绍了输入和校验,下面来看看寄存器。
Registers 寄存器
寄存器是一个工做流的全局变量。和propertyset相似,它能够在工做流实例的任意地方被获取。和propertyset不一样的是,它不是一个持久化的数据,而是每次调用时都须要从新计算的数据。
它能够被用在什么地方呢?在咱们的文档管理系统里面,若是定义了一个“document”的寄存器,那么对于函数、条件、脚原本说就是很是有用的:能够用它来得到正在被编辑的文档。
寄存器地值会被放在临时变量(transientVars map)里,这样可以在任意地方得到它。
定义一个寄存器和函数、条件的一个重要区别是,它并非依靠特定的调用(不用关心当前的步骤,或者是输入数据,它只是简单地暴露一些数据而已),因此它不用临时变量里的值。
寄存器必须实现Register接口,而且被定义在流程定义文件的头部,在初始化动做以前。
举例来讲,咱们将会使用一个osworkflow内置的寄存器:LogRegister。这个寄存器简单的添加一个“log”变量,可以让你使用Jakarta的commons-logging输出日志信息。它的好处是会在每条信息前添加工做流实例的ID。
<registers>
<register type="class" variable-name="log">
<arg name="class.name">
com.opensymphony.workflow.util.LogRegister
</arg>
<arg name="addInstanceId">true</arg>
<arg name="Category">workflow</arg>
</register>
</registers>这样咱们定义了一个可用的“log”变量,能够经过其余的pre-function的脚本里面使用它:
<function type="beanshell">
<arg name="script">transientVars.get("log".info("executing action 2"
</arg>
</function>日志输出将会在前面添加工做流实例的ID
结论
这个教程的目的是但愿能够阐明一些主要的osworkflow概念。你还能够经过API和流程定义格式去获取更多的信息。有一些更高级的特性没有在此提到,好比splits 分支、joins 链接, nested conditions 复合条件、auto stpes 自动步骤等等。你能够经过阅读手册来得到更进一步的理解。
osworkflow基础配置tomcat5+oracle8+win2k(1)
首先,下载URL https://osworkflow.dev.java.net/files/documents/635/27138/osworkflow-2.8.0.zip
。解压后。
一、将osworkflow-2.8.0-example.war拷贝至tomcat的webapp下,启动tomcat,访问http://localhost/osworkflow-2.8.0-example。
二、src/webapp直接拷贝到%tomcat_home%/webapp,还须要拷贝lib os.war里面有,拷贝主要是war部署的路径比较讨厌。
osworkflow提供了多种持久化机制MemoryStore (default), SerializableStore, JDBCStore, OfbizStore等等。因为下载的example是为了方便初学者尽快的将程序运行起来,因此采用了MemoryStore。呵呵,实际的系统可不会让数据全呆在内存里哦。改为JDBCStore试试。
一、修改tomcat的sever.xml(中间=后面须要加双引号) 添加:
<Context path=/osworkflow docBase=osworkflow debug=5 reloadable=true crossContext=true>
<Logger className=org.apache.catalina.logger.FileLogger
prefix=localhost_osworkflow_log. suffix=.txt
timestamp=true/>
<Resource name= jdbc/mydb auth=Container
type=javax.sql.DataSource/>
<ResourceParams name=jdbc/mydb>
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>oracle.jdbc.driver.OracleDriver</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:oracle:thin:@127.0.0.1:1521:orcl</value>
</parameter>
<parameter>
<name>username</name>
<value>oswf</value>
</parameter>
<parameter>
<name>password</name>
<value>oswf</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>20</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>10</value>
</parameter>
<parameter>
<name>maxWait</name>
<value>-1</value>
</parameter>
</ResourceParams>
</Context>
二、修改WEB-INF/classes/osworkflow.xml(红色部分根据您的数据库做相应修改)
<osworkflow>
<persistence class=com.opensymphony.workflow.spi.jdbc.JDBCWorkflowStore>
<!-- For jdbc persistence, all are required. -->
<property key=datasource value= jdbc/mydb />
<property key=entry.sequence value= SELECT seq_os_wfentry.nextVal from dual />
<property key=entry.table value=OS_WFENTRY/>
<property key=entry.id value=ID/>
<property key=entry.name value=NAME/>
<property key=entry.state value=STATE/>
<property key=step.sequence value= SELECT seq_os_currentsteps.nextVal from dual />
<property key=history.table value=OS_HISTORYSTEP/>
<property key=current.table value=OS_CURRENTSTEP/>
<property key=historyPrev.table value=OS_HISTORYSTEP_PREV/>
<property key=currentPrev.table value=OS_CURRENTSTEP_PREV/>
<property key=step.id value=ID/>
<property key=step.entryId value=ENTRY_ID/>
<property key=step.stepId value=STEP_ID/>
<property key=step.actionId value=ACTION_ID/>
<property key=step.owner value=OWNER/>
<property key=step.caller value=CALLER/>
<property key=step.startDate value=START_DATE/>
<property key=step.finishDate value=FINISH_DATE/>
<property key=step.dueDate value=DUE_DATE/>
<property key=step.status value=STATUS/>
<property key=step.previousId value=PREVIOUS_ID/>
</persistence>
<factory class=com.opensymphony.workflow.loader.XMLWorkflowFactory>
<property key=resource value=workflows.xml />
</factory>
</osworkflow>
三、在WEB-INF/classes里新建propertyset.xml
<propertysets>
<propertyset name=jdbc
class=com.opensymphony.module.propertyset.database.JDBCPropertySet>
<arg name=datasource value= jdbc/mydb />
<arg name=table.name value=OS_PROPERTYENTRY/>
<arg name=col.globalKey value=GLOBAL_KEY/>
<arg name=col.itemKey value=ITEM_KEY/>
<arg name=col.itemType value=ITEM_TYPE/>
<arg name=col.string value=STRING_VALUE/>
<arg name=col.date value=DATE_VALUE/>
<arg name=col.data value=DATA_VALUE/>
<arg name=col.float value=FLOAT_VALUE/>
<arg name=col.number value=NUMBER_VALUE/>
</propertyset>
</propertysets>
四、修改WEB-INF/classes下的osuser.xml
<opensymphony-user>
<provider class=com.opensymphony.user.provider.jdbc.JDBCAccessProvider>
<property name=user.table>OS_USER</property>
<property name=group.table>OS_GROUP</property>
<property name=membership.table>OS_MEMBERSHIP</property>
<property name=user.name>USERNAME</property>
<property name=user.password>PASSWORDHASH</property>
<property name=group.name>GROUPNAME</property>
<property name=membership.userName>USERNAME</property>
<property name=membership.groupName>GROUPNAME</property>
<property name=datasource>java:comp/env/ jdbc/mydb </property>
</provider>
<provider class=com.opensymphony.user.provider.jdbc.JDBCCredentialsProvider>
<property name=user.table>OS_USER</property>
<property name=group.table>OS_GROUP</property>
<property name=membership.table>OS_MEMBERSHIP</property>
<property name=user.name>USERNAME</property>
<property name=user.password>PASSWORDHASH</property>
<property name=group.name>GROUPNAME</property>
<property name=membership.userName>USERNAME</property>
<property name=membership.groupName>GROUPNAME</property>
<property name=datasource>java:comp/env /jdbc/mydb </property>
</provider>
<provider class=com.opensymphony.user.provider.jdbc.JDBCProfileProvider>
<property name=user.table>OS_USER</property>
<property name=group.table>OS_GROUP</property>
<property name=membership.table>OS_MEMBERSHIP</property>
<property name=user.name>USERNAME</property>
<property name=user.password>PASSWORDHASH</property>
<property name=group.name>GROUPNAME</property>
<property name=membership.userName>USERNAME</property>
<property name=membership.groupName>GROUPNAME</property>
<property name=datasource>java:comp/env/ jdbc/mydb </property>
</provider>
<!--
Authenticators can take properties just like providers.
This smart authenticator should work for 'most' cases - it dynamically looks up
the most appropriate authenticator for the current server.
-->
<authenticator class=com.opensymphony.user.authenticator.SmartAuthenticator />
</opensymphony-user>
五、在sql-plus里运行下载包里的 src\etc\deployment\jdbc\oracle.sql
六、启动tomcat
七、OK。
八、以上都是借鉴过来的,
九、在os_user中加入test用户,pass空,登录提示空指针,郁闷!!!
用osworkflow写一个请假例子
osworkflow扩展很是容易,跟咱们的应用结合起来使用也很容易。假设一个请假流程:员工请假,须要通过部门经理和人力资源部经理两人共同审批,只有当两人都许可时才经过,任一人驳回就失效,也就是一个and split和and Join流程,而且咱们附加一个要求,当发送请假请求、许可和驳回这几个操做时都将发送一条消息给相应的用户。
流程定义文件以下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
"http://www.opensymphony.com/osworkflow/workflow_2_7.dtd">
<workflow>
<initial-actions>
<action id="0" name="开始">
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Underway" step="1" owner="${caller}" />
</results>
</action>
</initial-actions>
<steps>
<step id="1" name="填假单">
<external-permissions>
<permission name="permA">
<restrict-to>
<conditions type="AND">
<condition type="class"><!--流程处于Underway状态(流程已经启动)-->
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
</permission>
</external-permissions>
<actions>
<action id="1" name="送出">
<restrict-to>
<conditions type="AND">
<condition type="class"><!--流程处于Underway状态(流程已经启动)-->
<arg name="class.name">
com.opensymphony.workflow.util.StatusCondition
</arg>
<arg name="status">Underway</arg>
</condition>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.AllowOwnerOnlyCondition
</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
split="1" status="Queued">
<post-functions>
<function type="class">
<arg name="class.name">
net.rubyeye.leavesys.service.workflow.SendRemindInfFunction
</arg>
<arg name="groupName">
AND (GROUPNAME='dept_manager' or
GROUPNAME='comp_manager')
</arg>
<arg name="content">
you have leavemsg to
check!please check it!
</arg>
</function>
</post-functions>
</unconditional-result>
</results>
</action>
</actions>
</step>
<step id="2" name="部门经理批假单">
<actions>
<action id="2" name="准许">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.OSUserGroupCondition
</arg>
<arg name="group">dept_manager</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
<function type="beanshell">
<arg name="script">
propertySet.setString("action1",
"success");
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" join="1" owner="${caller}" />
</results>
</action>
<action id="3" name="驳回">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.OSUserGroupCondition
</arg>
<arg name="group">dept_manager</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
<function type="beanshell">
<arg name="script">
propertySet.setString("action1",
"fail");
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" join="2" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="3" name="公司经理批假单">
<actions>
<action id="4" name="准许">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.OSUserGroupCondition
</arg>
<arg name="group">comp_manager</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
<function type="beanshell">
<arg name="script">
propertySet.setString("action2",
"success");
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" join="1" owner="${caller}" />
</results>
</action>
<action id="5" name="驳回">
<restrict-to>
<conditions>
<condition type="class">
<arg name="class.name">
com.opensymphony.workflow.util.OSUserGroupCondition
</arg>
<arg name="group">dept_manager</arg>
</condition>
</conditions>
</restrict-to>
<pre-functions>
<function type="class">
<arg name="class.name">
com.opensymphony.workflow.util.Caller
</arg>
</function>
<function type="beanshell">
<arg name="script">
propertySet.setString("action2",
"fail");
</arg>
</function>
</pre-functions>
<results>
<unconditional-result old-status="Finished"
status="Queued" join="2" owner="${caller}" />
</results>
</action>
</actions>
</step>
<step id="4" name="中止" />
</steps>
<splits>
<split id="1">
<unconditional-result old-status="Finished" status="Queued"
step="2" />
<unconditional-result old-status="Finished" status="Queued"
step="3" />
</split>
</splits>
<joins>
<join id="1">
<conditions type="AND">
<condition type="beanshell">
<arg name="script">
<![CDATA[
"Finished".equals(jn.getStep(2).getStatus()) &&
"Finished".equals(jn.getStep(3).getStatus())&&"success".equals(propertySet.getString("action1"))&&
"success".equals(propertySet.getString("action2"))
]]>
</arg>
</condition>
</conditions>
<unconditional-result old-status="Finished" status="Queued"
step="4"/>
</join>
<join id="2">
<conditions type="OR">
<condition type="beanshell">
<arg name="script">
<![CDATA[
"Finished".equals(jn.getStep(2).getStatus()) &&"fail".equals(propertySet.getString("action1"))
]]>
</arg>
</condition>
<condition type="beanshell">
<arg name="script">
<![CDATA[
"Finished".equals(jn.getStep(3).getStatus())&&"fail".equals(propertySet.getString("action2"))
]]>
</arg>
</condition>
</conditions>
<unconditional-result old-status="Finished" step="4"
status="Queued">
<post-functions>
<function type="class">
<arg name="class.name">
net.rubyeye.leavesys.service.workflow.SendRemindInfFunction
</arg>
<arg name="groupName">
AND GROUPNAME='employee'
</arg>
<arg name="content">
you leveamsg is fail!!!
</arg>
</function>
</post-functions>
</unconditional-result>
</join>
</joins>
</workflow>
请注意,咱们在许可或者经过的时候propertySet.setString("action2",......),propertySet.setString("action3",......),而后在join点判断,若是两个都是success,流程结束;若是一个是fail,就发送一个消息给员工。
发送消息的function像这样:
package net.rubyeye.leavesys.service.workflow;
import java.sql.SQLException;
import java.util.Map;
import net.rubyeye.leavesys.domain.RemindInf;
import net.rubyeye.leavesys.service.ManagerFactory;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.FunctionProvider;
import com.opensymphony.workflow.WorkflowException;
public class SendRemindInfFunction implements FunctionProvider {
public void execute(Map transientVars, Map args, PropertySet ps)
throws WorkflowException {
String groupName = (String) args.get("groupName");
String content = (String) args.get("content");
RemindInf remindInf = new RemindInf();
remindInf.setContent(content);
try {
ManagerFactory.getRemindService().addRemindInfByGroupName(
groupName, remindInf);
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
获得两个参数groupName和content(消息内容),调用业务对象发送消息。
完整代码下载在《LeaveSystem》
代码用到了本身过去写的一个MVC框架和持久层,对此有兴趣的参考这三篇文章:
《设计本身的MVC框架》
《设计模式之事务处理》
《使用Annotation设计持久层》
若是仅仅是想了解osworkflow的应用,建议您跑下流程,读读相关几个业务类(LeaveServiceImpl.java,SendRemindInfFunction.java,service包下)便可。解压缩后的文件能够直接导入myeclipse工程,部署在tomcat下,数据库用的是oracle。跑起来之后能够用3个用户登陆,test是雇员组,dennis是部门经理组,jordan是公司经理,都不须要密码。写的比较简单,只是实验性质,见谅。
我认为使用osworkflow,只要了解了它的表结构和主要原理,根据你的业务须要结合几张主要表(os_wfentry,os_currentstep,os_historystep等)合理设计数据库和业务流程,能够省去过去为每一个业务流程对象建立的一大堆flag(标志,目前的流程状态)的累赘,充分利用工做流的威力。好比为部门经理和人力资源部经理显示不一样的须要审批的假单列表,只要结合os_historystep表进行联合查询,部门经理的应该是执行了未执行acion2,step在3的;而人力资源部经理获得的一样是step在3,action未执行3的。
手痒痒,很想把去年为一家公司写的绩效考核系统改写一下,当时设计的一个contract对象拥有7,8个flag来标志合约状态(直接上级审核,人力资源评价,KPI评价等),搞的很是混乱,并且流程写死在代码里,若是之后要改变考核流程,只有从新写过一套。不过那家公司是国有企业,每一年的固定的预算费用必定要花掉,反正你们一块儿赚国家的钱嘛。