接口選擇:
osworkflow提供幾種實現(xiàn)com.opensymphony.workflow.Workflow接口的類。
BasicWorkflow:
不提供事務支持,你可以通過持久層來實現(xiàn)事務處理。
Workflow wf = new BasicWorkflow(username)
這里的username是用來關(guān)聯(lián)當前請求的用戶。
EJBWorkflow:
用ejb容器來管理事務。在ejb-jar.xml中進行配置。
Workflow wf = new EJBWorkflow()
這里沒有必要想basicworkflow和ofbizworkflow那樣給出username。因為ejb容器已經(jīng)校驗過的。
Ofbizworkflow:
與basicworkflow比較相似,不同只在于需要事務支持的方法由ofbiz TransactionUtil calls來包裝。
創(chuàng)建新的工作流實例:
這里是以basicworkflow為例子
Workflow wf = new BasicWorkflow(username);
HashMap inputs = new HashMap();
inputs.put("docTitle", request.getParameter("title");
wf.initialize("workflowName", 1, inputs);
執(zhí)行action:
Workflow wf = new BasicWorkflow(username);
HashMap inputs = new HashMap();
inputs.put("docTitle", request.getParameter("title");
long id = Long.parseLong(request.getParameter("workflowId");
wf.doAction(id, 1, inputs);
查詢:
值得注意的是:并不是所有的 workflow stores支持查詢。當前的hibernate,jdbc和內(nèi)存工作流存儲支持查詢。Hibernate存儲不支持mixed-type查詢(如,一個查詢使用到了歷史和當前step contexts)。為了執(zhí)行一個查詢,需要構(gòu)造出一個WorkflowExpressionQuery對象。查詢方法是在這個對象上被調(diào)用的。
簡單查詢、嵌套查詢、mixed-context查詢(不支持hibernate工作流存儲)在docs文檔的5.4部分都有。
Step
大致相當于流程所在的位置。譬如企業(yè)年檢,年檢報告書在企業(yè)端算一個step,在工商局算第二個step,在復核窗口算第三個step。每個step可以有多種狀態(tài)(status)和多個動作(action),用Workflow.getCurrentSteps()可以獲得所有當前的step(如果有并列流程,則可能同時有多個step,例如一次年檢可能同時位于“初審”step和“廣告經(jīng)營資格審查”step)。
Status
流程在某個step中的狀態(tài)。很容易理解,譬如“待認領(lǐng)”、“審核不通過”之類的。OSWorkflow中的狀態(tài)完全是由開發(fā)者自定義的,狀態(tài)判別純粹是字符串比對,靈活性相當強,而且可以把定義文件做得很好看。
Action
導致流程狀態(tài)變遷的動作。一個action典型地由兩部分組成:可以執(zhí)行此動作的條件(conditions),以及執(zhí)行此動作的結(jié)果(results)。條件可以用BeanShell腳本來判斷,因此具有很大的靈活性,幾乎任何與流程相關(guān)的東西都可以用來做判斷。
Result
執(zhí)行動作后的結(jié)果。這是個比較重要的概念。result分為兩種,conditional-result和unconditional-result。執(zhí)行一個動作之后,首先判斷所有conditional-result的條件是否滿足,滿足則使用該結(jié)果;如果沒有任何contidional-result滿足條件,則使用unconditional-result。unconditional-result需要指定兩部分信息:old-status,表示“當前step的狀態(tài)變成什么”;后續(xù)狀態(tài),可能是用step+status指定一個新狀態(tài),也可能進入split或者join。
conditional-result非常有用。還是以年檢為例,同樣是提交年檢報告書,“未提交”和“被退回”是不同的狀態(tài),在這兩個狀態(tài)基礎上執(zhí)行“提交”動作,結(jié)果分別是“初次提交”和“退回之后再次提交”。這時可以考慮在“提交”動作上用conditional-result。
Split/Join
流程的切分和融合。很簡單的概念,split提供多個result;join則判斷多個current step的狀態(tài),提供一個result。
* * *
熟悉這些概念,在流程定義中盡量使用中文,可以給業(yè)務代碼和表現(xiàn)層帶來很多方便。
目的
這篇指導資料的目的是介紹OSWorkflow的所有概念,指導你如何使用它,并且保證你逐步理解OSWorkflow的關(guān)鍵內(nèi)容。
本指導資料假定你已經(jīng)部署OSWorkflow的范例應用在你的container上。范例應用部署是使用基于內(nèi)存的數(shù)據(jù)存儲,這樣你不需要擔心如何配置其他持久化的例子。范例應用的目的是為了說明如何應用OSWorkflow,一旦你精通了OSWorkflow的流程定義描述符概念和要素,應該能通過閱讀這些流程定義文件而了解實際的流程。
本指導資料目前有3部分:
1. 你的第一個工作流
2. 測試你的工作流
3. 更多的流程定義描述符概念
1. Your first workflow
創(chuàng)建描述符
首先,讓我們來定義工作流。你可以使用任何名字來命名工作流。一個工作流對應一個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內(nèi)容的合法性。你可以使用絕大多數(shù)的XML編輯工具來編輯它,并且可以highlight相應的錯誤。
步驟和動作
接下來我們來定義初始化動作和步驟。首先需要理解的OSWorkflow重要概念是steps (步驟) 和 actions (動作)。一個步驟是工作流所處的位置,比如一個簡單的工作流過程,它可能從一個步驟流轉(zhuǎn)到另外一個步驟(或者有時候還是停留在一樣的步驟)。舉例來說,一個文檔管理系統(tǒng)的流程,它的步驟名稱可能有“First Draft - 草案初稿”,“Edit Stage -編輯階段”,“At publisher - 出版商”等。
動作指定了可能發(fā)生在步驟內(nèi)的轉(zhuǎn)變,通常會導致步驟的變更。在我們的文件管理系統(tǒng)中,在“草案初稿”這個步驟可能有“start first draft - 開始草案初稿”和“complete first draft - 完成草案初稿”這樣2個動作。
簡單的說,步驟是“在哪里”,動作是“可以去哪里”。
初始化步驟是一種特殊類型的步驟,它用來啟動工作流。在一個工作流程開始前,它是沒有狀態(tài),不處在任何一個步驟,用戶必須采取某些動作才能開始這個流程。這些特殊步驟被定義在 <initial-actions>。
在我們的例子里面,假定只有一個簡單的初始化步驟:“Start Workflow”,它的定義在里面
<initial-actions>:
<action id="1" name="Start Workflow">
<results>
<unconditional-result old-status="Finished" status="Queued" step="1"/>
</results>
</action>
這個動作是最簡單的類型,只是簡單地指明了下一個我們要去的步驟和狀態(tài)。
工作流狀態(tài)
工作流狀態(tài)是一個用來描述工作流程中具體步驟狀態(tài)的字符串。在我們的文檔管理系統(tǒng)中,在“草案初稿”這個步驟可能有2個不同的狀態(tài):“Underway - 進行中”和“Queued - 等候處理中”
我們使用“Queued”指明這個條目已經(jīng)被排入“First Draft”步驟的隊列。比如說某人請求編寫某篇文檔,但是還沒有指定作者,那么這個文檔在“First Draft”步驟的狀態(tài)就是“Queued”。“Underway”狀態(tài)被用來指明一個作者已經(jīng)挑選了一篇文檔開始撰寫,而且可能正在鎖定這篇文檔。
第一個步驟
讓我們來看第一個步驟是怎樣被定義在<steps>元素中的。我們有2個動作:第一個動作是保持當前步驟不變,只是改變了狀態(tài)到“Underway”,第二個動作是移動到工作流的下一步驟。我們來添加如下的內(nèi)容到<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屬性是用來指明當前步驟完成以后的狀態(tài)是什么,在大多數(shù)的應用中,通常用"Finished"表示。
上面定義的這2個動作是沒有任何限制的。比如,一個用戶可以調(diào)用action 2而不用先調(diào)用action 1。很明顯的,我們?nèi)绻麤]有開始撰寫草稿,是不可能去完成一個草稿的。同樣的,上面的定義也允許你開始撰寫草稿多次,這也是毫無意義的。我們也沒有做任何的處理去限制其他用戶完成別人的草稿。這些都應該需要想辦法避免。
讓我們來一次解決這些問題。首先,我們需要指定只有工作流的狀態(tài)為“Queued”的時候,一個caller (調(diào)用者)才能開始撰寫草稿的動作。這樣就可以阻止其他用戶多次調(diào)用開始撰寫草稿的動作。我們需要指定動作的約束,約束是由Condition(條件)組成。
條件
OSWorkflow 有很多有用的內(nèi)置條件可以使用。在此相關(guān)的條件是“StatusCondition - 狀態(tài)條件”。 條件也可以接受參數(shù),參數(shù)的名稱通常被定義在javadocs里(如果是使用Java Class實現(xiàn)的條件的話)。在這個例子里面,狀態(tài)條件接受一個名為“status”的參數(shù),指明了需要檢查的狀態(tài)條件。我們可以從下面的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>希望對于條件的理解現(xiàn)在已經(jīng)清楚了。上面的條件定義保證了動作1只能在當前狀態(tài)為“Queued”的時候才能被調(diào)用,也就是說在初始化動作被調(diào)用以后。
函數(shù)
接下來,我們想在一個用戶開始撰寫草稿以后,設置他為“owner”。為了達到這樣的目的,我們需要做2件事情:
1) 通過一個函數(shù)設置“caller”變量在當前的環(huán)境設置里。
2) 根據(jù)“caller”變量來設置“owner”屬性。
函數(shù)是OSWorkflow的一個功能強大的特性。函數(shù)基本上是一個在工作流程中的工作單位,他不會影響到流程本身。舉例來說,你可能有一個“SendEmail”的函數(shù),用來在某些特定的流程流轉(zhuǎn)發(fā)生時來發(fā)送email提醒。
函數(shù)也可以用來添加變量到當前的環(huán)境設置里。變量是一個指定名稱的對象,可以用來在工作流中被以后的函數(shù)或者腳本使用。
OSWorkflow提供了一些內(nèi)置的常用函數(shù)。其中一個稱為“Caller”,這個函數(shù)會獲得當前調(diào)用工作流的用戶,并把它放入一個名為“caller”的字符型變量中。
因為我們需要追蹤是哪個用戶開始了編寫草稿,所以可以使用這個函數(shù)來修改我們的動作定義:
<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”。這樣能夠保證只有開始撰寫這份草稿的用戶才能完成它。而狀態(tài)條件確保了只有在“Underway”狀態(tài)下的流程才能調(diào)用“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>現(xiàn)在這個工作流的定義已經(jīng)完整了,讓我們來測試和檢查它的運行。
2. Testing your workflow
現(xiàn)在我們已經(jīng)完成了一個完整的工作流定義,下一步是檢驗它是否按照我們預想的方式執(zhí)行。
在一個快速開發(fā)環(huán)境中,最簡單的方法就是寫一個測試案例。通過測試案例調(diào)用工作流,根據(jù)校驗結(jié)果和捕捉可能發(fā)生的錯誤,來保證流程定義的正確性。
我們假設你已經(jīng)熟悉Junit和了解怎樣編寫測試案例。如果你對這些知識還不了解的話,可以去JUnit的網(wǎng)站查找、閱讀相關(guān)文檔。編寫測試案例會成為你的一個非常有用的工具。
在開始載入流程定義、調(diào)用動作以前,我們需要配置OSWorkflow的數(shù)據(jù)存儲方式和定義文件的位置等。
配置 osworkflow.xml
我們需要創(chuàng)建的第一個文件是 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>這個例子指明了我們準備使用內(nèi)存 (MemoryWorkflowStore) 來保存流程數(shù)據(jù)。這樣可以減少設置數(shù)據(jù)庫的相關(guān)信息,減少出問題的可能性。用內(nèi)存持久化對于測試來說是非常方便的。
Workflow factories
上面的配置文件還指明了我們工作流工廠(XMLWorkflowFactory),工作流工廠的主要功能是管理流程定義文件,包括讀取定義文件和修改定義文件的功能。通過'resource'這個屬性指明了采用通過從classpath中讀取流程定義文件的方式,按照這個定義,接下來我們需要在classpath中創(chuàng)建一個名為workflows.xml的文件。
workflows.xml 的內(nèi)容:
<workflows>
<workflow name="mytest" type="resource" location="myworkflow.xml"/>
</workflows>我們把 myworkflow.xml 和workflows.xml放在同一目錄,這樣它就能夠被工作流工廠讀取了。
這樣就完成了配置,接下來是初始化一個流程并調(diào)用它。
Initialising OSWorkflow
OSWorkflow 的調(diào)用模式相當簡單:通過一個主要的接口來執(zhí)行大部分操作。這個接口就是 Workflow interface,及其擴展 AbstractWorkflow 的實現(xiàn),例如EJBWorkflow 和 SOAPWorkflow. 為了簡單起見,我們使用最基本的一種: BasicWorkflow。
首先,我們來創(chuàng)建Workflow。在實際項目中,這個對象應該被放在一個全局的位置上以供重用,因為每次都創(chuàng)建一個新的Workflow對象是需要耗費比較昂貴的系統(tǒng)資源。在這里的例子,我們采用BasicWorkflow,它的構(gòu)建器由一個當前調(diào)用者的用戶名構(gòu)成,當然我們很少看到單用戶的工作流應用,可以參考其他的Workflow實現(xiàn)有不同的方式去獲得當前調(diào)用者。
為了簡單起見,我們采用BasicWorkflow來創(chuàng)建一個單一的用戶模式,避免編寫其他獲取用戶方法的麻煩。
這樣我們來創(chuàng)建一個'testuser'調(diào)用的workflow:
Workflow workflow = new BasicWorkflow("testuser";下一步是提供配置文件,在大多數(shù)情況下,只是簡單的傳遞一個DefaultConfiguration實例:
DefaultConfiguration config = new DefaultConfiguration();
workflow.setConfiguration(config);現(xiàn)在我們已經(jīng)創(chuàng)建并且配置好了一個workflow,接下來就是開始調(diào)用它了。
啟動和進行一個工作流程
首先我們需要調(diào)用initialize 方法來啟動一個工作流程,這個方法有3個參數(shù),workflow name (定義在workflows.xml里,通過workflow factory處理), action ID (我們要調(diào)用的初始化動作的ID),和初始化變量。 因為在例子里面不需初始化變量,所以我們只是傳遞一個null,
long workflowId = workflow.initialize("mytest", 1, null);我們現(xiàn)在已經(jīng)有了一個工作流實例,返回的workflowId可以在后續(xù)的操作中來代表這個實例。這個參數(shù)會在Workflow interface的絕大部分方法中用到。
檢驗工作流
現(xiàn)在讓我們來檢驗啟動的工作流實例是否按照我們所預期的那樣運行。根據(jù)流程定義,我們期望的當前步驟是第一步,而且應該可以執(zhí)行第一個動作(開始編寫草稿)。
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);
//校驗只有一個可執(zhí)行的動作
assertEquals("Unexpected number of available actions", 1,
availableActions.length);
//校驗這個步驟是1
assertEquals("Unexpected available action", 1, availableActions[0]);執(zhí)行動作
現(xiàn)在已經(jīng)校驗完了工作流實例正如我們所期望的到了第一個步驟,讓我們來開始執(zhí)行第一個動作:
workflow.doAction(workflowId, 1, null);這是簡單的調(diào)用第一個動作,工作流引擎根據(jù)指定的條件,改變狀態(tài)到‘Underway’,并且保持在當前步驟。
現(xiàn)在我們可以類似地調(diào)用第2個動作,它所需要的條件已經(jīng)都滿足了。
在調(diào)用完第2個動作以后,根據(jù)流程定義就沒有可用的動作了,getAvailableActions將會返回一個空數(shù)組。
Congratulations, 你已經(jīng)完成了一個工作流定義并且成功地調(diào)用了它。下一節(jié)我們將會講解osworkflow一些更深入的概念。
3. Further descriptor concepts
定義條件和函數(shù)
你也許已經(jīng)注意到,到目前為止,我們定義的條件和函數(shù)類型都是“class”。這種類型的條件和函數(shù)接受一個參數(shù):“class.name”,以此來指明一個實現(xiàn)FunctionProvider或Condition接口的完整類名。
在osworkflow里面也有一些其他內(nèi)置的類型,包括beanshell,無狀態(tài)的session bean,JNDI樹上的函數(shù)等。我們在下面的例子里使用beanshell類型。
Property sets
我們可能需要在工作流的任意步驟持久化一些少量數(shù)據(jù)。在osworkflow里,這是通過OpenSymphony的PropertySet library來實現(xiàn)。一個PropertySet基本上是一個可以持久化的類型安全map,你可以添加任意的數(shù)據(jù)到propertyset(一個工作流實例對應一個propertyset),并在以后的流程中再讀取這些數(shù)據(jù)。除非你特別指定操作,否則propertyset中的數(shù)據(jù)不會被清空或者被刪除。任意的函數(shù)和條件都可以和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,只是在當前的工作流調(diào)用的上下文內(nèi)有效。它包括當前的工作流實例,工作流定義等對應值的引用。你可以通過FunctionProvider的javadoc來查看這個map有那些可用的key。
還記得我們在教程的第2部分傳入的那個null嗎?如果我們不傳入null的話,那么這些輸入數(shù)據(jù)將會被添加到臨時變量的map里。
inputs 輸入
每次調(diào)用workflow的動作時可以輸入一個可選的map,可以在這個map里面包含供函數(shù)和條件使用的任何數(shù)據(jù),它不會被持久化,只是一個簡單的數(shù)據(jù)傳遞。
Validators 校驗器
為了讓工作流能夠校驗輸入的數(shù)據(jù),引入了校驗器的概念。一個校驗器和函數(shù),條件的實現(xiàn)方式非常類似(比如,它可以是一個class,腳本,或者EJB)。在這個教程里面,我們將會定義一個校驗器,在“finish first draft”這個步驟,校驗用戶輸入的數(shù)據(jù)“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>這樣,當我們執(zhí)行動作2的時候,這個校驗器將會被調(diào)用,并且檢驗我們的輸入。這樣在測試代碼里面,如果加上:
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,這個動作將不會被執(zhí)行。減少輸入的title字符,將會讓這個動作成功執(zhí)行。
我們已經(jīng)介紹了輸入和校驗,下面來看看寄存器。
Registers 寄存器
寄存器是一個工作流的全局變量。和propertyset類似,它可以在工作流實例的任意地方被獲取。和propertyset不同的是,它不是一個持久化的數(shù)據(jù),而是每次調(diào)用時都需要重新計算的數(shù)據(jù)。
它可以被用在什么地方呢?在我們的文檔管理系統(tǒng)里面,如果定義了一個“document”的寄存器,那么對于函數(shù)、條件、腳本來說就是非常有用的:可以用它來獲得正在被編輯的文檔。
寄存器地值會被放在臨時變量(transientVars map)里,這樣能夠在任意地方獲得它。
定義一個寄存器和函數(shù)、條件的一個重要區(qū)別是,它并不是依靠特定的調(diào)用(不用關(guān)心當前的步驟,或者是輸入數(shù)據(jù),它只是簡單地暴露一些數(shù)據(jù)而已),所以它不用臨時變量里的值。
寄存器必須實現(xiàn)Register接口,并且被定義在流程定義文件的頭部,在初始化動作之前。
舉例來說,我們將會使用一個osworkflow內(nèi)置的寄存器: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
結(jié)論
這個教程的目的是希望可以闡明一些主要的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。呵呵,實際的系統(tǒng)可不會讓數(shù)據(jù)全呆在內(nèi)存里哦。改成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(紅色部分根據(jù)您的數(shù)據(jù)庫作相應修改)
<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擴展非常容易,跟我們的應用結(jié)合起來使用也很容易。假設一個請假流程:員工請假,需要經(jīng)過部門經(jīng)理和人力資源部經(jīng)理兩人共同審批,只有當兩人都許可時才通過,任一人駁回就失效,也就是一個and split和and Join流程,并且我們附加一個要求,當發(fā)送請假請求、許可和駁回這幾個操作時都將發(fā)送一條消息給相應的用戶。
流程定義文件如下:
<?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狀態(tài)(流程已經(jīng)啟動)-->
<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狀態(tài)(流程已經(jīng)啟動)-->
<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="部門經(jīng)理批假單">
<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="公司經(jīng)理批假單">
<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,流程結(jié)束;如果一個是fail,就發(fā)送一個消息給員工。
發(fā)送消息的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();
}
}
}
得到兩個參數(shù)groupName和content(消息內(nèi)容),調(diào)用業(yè)務對象發(fā)送消息。
完整代碼下載在《LeaveSystem》
代碼用到了自己過去寫的一個MVC框架和持久層,對此有興趣的參考這三篇文章:
《設計自己的MVC框架》
《設計模式之事務處理》
《使用Annotation設計持久層》
如果僅僅是想了解osworkflow的應用,建議您跑下流程,讀讀相關(guān)幾個業(yè)務類(LeaveServiceImpl.java,SendRemindInfFunction.java,service包下)即可。解壓縮后的文件可以直接導入myeclipse工程,部署在tomcat下,數(shù)據(jù)庫用的是oracle。跑起來以后可以用3個用戶登錄,test是雇員組,dennis是部門經(jīng)理組,jordan是公司經(jīng)理,都不需要密碼。寫的比較簡單,只是實驗性質(zhì),見諒。
我認為使用osworkflow,只要了解了它的表結(jié)構(gòu)和主要原理,根據(jù)你的業(yè)務需要結(jié)合幾張主要表(os_wfentry,os_currentstep,os_historystep等)合理設計數(shù)據(jù)庫和業(yè)務流程,可以省去過去為每個業(yè)務流程對象創(chuàng)建的一大堆flag(標志,目前的流程狀態(tài))的累贅,充分利用工作流的威力。比如為部門經(jīng)理和人力資源部經(jīng)理顯示不同的需要審批的假單列表,只要結(jié)合os_historystep表進行聯(lián)合查詢,部門經(jīng)理的應該是執(zhí)行了未執(zhí)行acion2,step在3的;而人力資源部經(jīng)理得到的同樣是step在3,action未執(zhí)行3的。
手癢癢,很想把去年為一家公司寫的績效考核系統(tǒng)改寫一下,當時設計的一個contract對象擁有7,8個flag來標志合約狀態(tài)(直接上級審核,人力資源評價,KPI評價等),搞的非?;靵y,而且流程寫死在代碼里,如果以后要改變考核流程,只有重新寫過一套。不過那家公司是國有企業(yè),每年的固定的預算費用一定要花掉,反正大家一起賺國家的錢嘛。