接口選擇:
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為例子




執行action:





查詢:
值得注意的是:并不是所有的 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的關鍵內容。
本指導資料假定你已經部署OSWorkflow的范例應用在你的container上。范例應用部署是使用基于內存的數據存儲,這樣你不需要擔心如何配置其他持久化的例子。范例應用的目的是為了說明如何應用OSWorkflow,一旦你精通了OSWorkflow的流程定義描述符概念和要素,應該能通過閱讀這些流程定義文件而了解實際的流程。
本指導資料目前有3部分:
1. 你的第一個工作流
2. 測試你的工作流
3. 更多的流程定義描述符概念
1. Your first workflow
創建描述符
首先,讓我們來定義工作流。你可以使用任何名字來命名工作流。一個工作流對應一個XML格式的定義文件。讓我們來開始新建一個“myworkflow.xml”的文件,這是樣板文件:
<?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
。解壓后。
1、將osworkflow-2.8.0-example.war拷貝至tomcat的webapp下,啟動tomcat,訪問http://localhost/osworkflow-2.8.0-example。
2、src/webapp直接拷貝到%tomcat_home%/webapp,還需要拷貝lib os.war里面有,拷貝主要是war部署的路徑比較討厭。
osworkflow提供了多種持久化機制MemoryStore (default), SerializableStore, JDBCStore, OfbizStore等等。由于下載的example是為了方便初學者盡快的將程序運行起來,所以采用了MemoryStore。呵呵,實際的系統可不會讓數據全呆在內存里哦。改成JDBCStore試試。
1、修改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>
2、修改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>
3、在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>
4、修改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>
5、在sql-plus里運行下載包里的 src\etc\deployment\jdbc\oracle.sql
6、啟動tomcat
7、OK。
8、以上都是借鑒過來的,
9、在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評價等),搞的非常混亂,而且流程寫死在代碼里,如果以后要改變考核流程,只有重新寫過一套。不過那家公司是國有企業,每年的固定的預算費用一定要花掉,反正大家一起賺國家的錢嘛。