【轉】osworkflow教程

寫的很詳細,收藏的!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

Workflow wf  =   new  BasicWorkflow(username);
HashMap inputs 
=   new  HashMap();
inputs.put(
" docTitle " , request.getParameter( " title " );
wf.initialize(
" workflowName " 1 , inputs);



執行action:
shell

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和內存工做流存儲支持查詢。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評價等),搞的很是混亂,並且流程寫死在代碼裏,若是之後要改變考覈流程,只有從新寫過一套。不過那家公司是國有企業,每一年的固定的預算費用必定要花掉,反正你們一塊兒賺國家的錢嘛。

相關文章
相關標籤/搜索