用戶/群組的管理是由UserManager來完成的,它包含于一個單獨的jar包內。我們可以這樣使用UserManager:
UserManager um = UserManager.getInstance();
User test = um.createUser("test");
test.setPassword("test");
Group foos = um.createGroup("foos");
test.addToGroup(foos);
利用UserManager也可以實現用戶驗證功能:
UserManager um = UserManager.getInstance();
boolean authenticated = false;
authenticated = um.getUser(username).authenticate(password);
if (authenticated) {
session.setAttribute("username");
……
} else {
……
}
關于step執行人的跟蹤,首先我們可以在創建流程的時候傳入調用者(caller)名稱,比如:
Workflow wf = new BasicWorkflow((String) session.getAttribute("username"));
BasicWorkflow會負責創建一個實現了WorkflowContext接口的實例,其中記錄了caller的信息。利用com.opensymphony.workflow.util.Caller,可以將WorkflowContext中的caller隨時植入transientVars中,以供后續的條件判斷。為此,我們需要在流程定義文件中的適當位置加入如下定義(比如initial-actions中的pre-functions節點處):
<pre-functions>
<function type="class">
<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
</function>
</pre-functions>
Caller是一個FunctionProvider,其excute方法中包含了如下代碼:
WorkflowContext context = (WorkflowContext) transientVars.get("context");
transientVars.put("caller", context.getCaller());
同時,我們還可以指定流程中某個step的執行人(owner),只需要在action的results節點處為其指定owner屬性:
<step id="2″ name="Edit Doc">
<actions>
<action id="2″ name="Sign Up For Editing">
……
<results>
<unconditional-result old-status="Finished” status="Underway” step="2″ owner="${caller}"/>
</results>
利用caller和owner信息,我們可以在流程定義文件的condition節點中以多種形式指定限定條件,比如,利用腳本限定只允許caller為test的用戶觸發某結果:
<result old-status="Finished">
<condition type="beanshell">
<arg name="script">
propertySet.getString("caller").equals("test")
</arg>
</condition>
……
</result>
又比如,利用util包中的OSUserGroupCondition限定僅當caller為foos群組中的用戶時,才觸發action:
<action id="1″ name="Start Workflow">
……
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
<arg name="group">foos</arg>
</condition>
再比如:利用util包中的AllowOwnerOnlyCondition限定僅當caller等于owner時,才觸發action:
<action id="1″ name="Start Workflow">
……
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
</condition>
OSWorkflow解讀之八
WorkflowQuery及其有關查詢類>>
我們知道,通常人們總是希望了解流程當前的運行狀況,因此就需要工作流引擎在提供流程流轉的基本功能的同時,還需要提供查詢功能。在osworkflow中,查詢功能是由WorkflowQuery及其相關類提供的。
WorkflowQuery提供了兩種類型的構造函數:
public WorkflowQuery(int field, int type, int operator, Object value)
public WorkflowQuery(WorkflowQuery left, int operator, WorkflowQuery right)
我們可以利用第一個構造函數創建基本的WorkflowQuery實例,然后利用第二個構造函數組織裝配。以查詢執行者是“test”且狀態是“Underway”的step實例為例:
WorkflowQuery queryLeft = new WorkflowQuery(
WorkflowQuery.OWNER, WorkflowQuery.CURRENT, WorkflowQuery.EQUALS, “test");
WorkflowQuery queryRight = new WorkflowQuery(
WorkflowQuery.STATUS, WorkflowQuery.CURRENT, WorkflowQuery.EQUALS, “Underway");
WorkflowQuery query = new WorkflowQuery(
queryLeft, WorkflowQuery.AND, queryRight);
List workflows = wf.query(query);
for (Iterator iterator = workflows.iterator(); iterator.hasNext();)
Long wfId = (Long) iterator.next();
}
裝配好的查詢條件,將會傳入AbstractoWorkflow的query方法中,然后再由WorkflowStore的query方法執行具體查詢操作。不同的WorkflowStore實例其查詢方式不盡相同,以MemoryWorkflowStore為例,它將遍歷所有位于cache中的流程,然后將滿足條件的流程ID放入一個ArrayList中返回,查詢的核心代碼采用了遞歸調用的形式:
if (query.getLeft() == null) {
return queryBasic(entryId, query);
} else {
int operator = query.getOperator();
WorkflowQuery left = query.getLeft();
WorkflowQuery right = query.getRight();
switch (operator) {
case WorkflowQuery.AND:
return query(entryId, left) && query(entryId, right);
case WorkflowQuery.OR:
return query(entryId, left) || query(entryId, right);
case WorkflowQuery.XOR:
return query(entryId, left) ^ query(entryId, right);
}
}
這里的queryBasic再次使用了遞歸調用的方式,詳細情況可以查看osworkflow的源代碼。通過這樣的方式,可以滿足任意復雜的流程查詢條件指定。
OSWorkflow解讀之七
Descriptors>>
在osworkflow的很多地方都會看到Descriptor的使用,最重要的一個是前面提到的WorkflowDescriptor。除此以外還有,ActionDescriptor、ConditionsDescriptor、ConditionDescriptor、FunctionDescriptor、PermissionDescriptor等等。這些類均位于com.opensymphony.workflow.loader包中。它們的作用,除了提供getter方法外(類似model的角色),還負責xml文件的讀取與寫入。WorkflowDescriptor在執行xml文件的讀寫時,如果涉及具體的流程定義元素,將會交由對應的Descriptor類來完成。比如,在WorkflowDescriptor的writeXML方法中,對initial action的序列化是這么實現的:
XMLUtil.printIndent(out, indent++);
out.println("<initial-actions>");
for (int i = 0; i < initialActions.size(); i++) {
ActionDescriptor action = (ActionDescriptor) initialActions.get(i);
action.writeXML(out, indent);
}
XMLUtil.printIndent(out, –indent);
out.println("</initial-actions>");
OSWorkflow解讀之六
pre function和post function>>
pre function和post function是osworkflow提供的又一特色,它為某項執行邏輯提供了前驅和后繼處理,運用十分靈活。并且,osworkflow為許多元件的執行邏輯都配備了pre function和post function的調用時機。這一點也可以從AbstractWorkflow.doAction的執行邏輯中看到??梢允褂煤蚿re function和post function的元件包括:action,result/unconditional result,step,split,join。
ScriptVariableParser>>
作為osworkflow的一個util class,ScriptVariableParser的主要功能是將給定字串中的${var}替換成相應的value。這意味著我們可以在許多地方使用類似于Ant中引用property的語法,來進一步提高靈活性。比如:
<results>
<unconditional -result old-status="Finished” status="Underway” step="1″ owner="${caller}"/>
</results>
在這里,unconditional result的owner屬性將被caller的實際值所替代。
TransientVars和PropertySet>>
在osworkflow的流程流轉過程中,時常會用到Transient Vars和Property Set。這兩個工具用來暫存一些臨時信息或者在step間傳遞一些共享信息,比如:context信息,workflow entry信息,以及上面提到的${var}的value,等等。
Transient Vars實際上就是一個普通的Map,至于Property Set,則是opensymphony的另一個獨立模塊,需要單獨下載jar包。與Transient Vars將信息暫存與內存不同,Property Set還支持數據庫存儲(JDBCPropertySet)
Register>>
為了更進一步提高靈活性,osworkflow還提供了Register功能。我們可以定義自己的Register,以執行特殊任務,并在流程定義文件中注明,該Register便會被動態注冊到Transient Vars中,以備隨時取用。以下便是一個典型的使用場景:
<register type="class” variable-name="log">
<arg name="class.name">com.opensymphony.workflow.util.LogRegister</arg>
<arg name="addInstanceId">true</arg>
</register>
</registers>
<arg name="script">transientVars.get("log").info("function called");</arg>
</function>
此外,為了方便使用,osworkflow的util包中還預定義了大量的Condition和FunctionProvider,以及其他的一些輔助類,比如:StatusCondition、AllowOwnerOnlyCondition、BeanShellCondition、Caller、EJBInvoker、ScheduleJob。
借助這些設施,osworkflow的擴展性、靈活性、易用性,得到了極大的體現。
OSWorkflow解讀之五
Schedule>>
在osworkflow中提供了定時執行某項任務的功能,這主要得益于opensymphony的另一個項目——Quatz,該項目為工作的定期執行提供了底層設施支持。為此,我們需要引用quatrz.jar包,好在osworkflow的下載包中已經包含了該文件。
為了實現任務定時,我們需要在流程定義文件中做類似如下的配置:
<arg name="class.name">com.opensymphony.workflow.util.ScheduleJob</arg>
<arg name="triggerId">1</arg>
<arg name="jobName">testJob</arg>
<arg name="triggerName">testTrigger</arg>
<arg name="groupName">test</arg>
<arg name="repeat">10</arg>
<arg name="repeatDelay">2000</arg>
<arg name="cronExpression">0,5,10,15,20,25,30,35,40,45,50,55 * * * * ?</arg>
<arg name="username">test</arg>
<arg name="password">test</arg>
<arg name="local">true</arg>
<arg name="schedulerStart">true</arg>
</function>
ScheduleJob是一個FunctionProiver,因此具有execute方法。在該方法執行期間,ScheduleJob將會讀取這些配置參數,創建好job實例(實際上是一個JobDetail實例)和trigger實例,然后啟動schedule。大致流程如下:
- 根據傳入的shedulerName參數,利用org.quartz.impl.StdSchedulerFactory的getScheduler方法創建sheduler實例,該實例實現了org.quartz.Scheduler接口;
- 根據傳入的jobClass參數,決定創建何種Job實例,osworkflow自身提供了兩種選擇:WorkflowJob和LocalWorkflowJob。前者支持SOAP協議,后者則是本地調用,它們都實現了org.quartz.Job接口。
- 創建一個描述Job信息的JobDetail實例,并做好初始設置;
- 若傳入參數中未指定cronExpression,則創建SimpleTrigger,并設置好startDate、endDate、repeat,否則創建CronTrigger
- 在jobDetail和trigger準備完畢后,就可以啟動schedule了:
s.addJob(jobDetail, true);
s.scheduleJob(trigger);
s.start();
scheduler中應該可以同時維護多個job和trigger,當trigger的觸發條件滿足后,將會激活真正的job實例。由于,scheduler中只保存了jobDetail的實例,因此我猜想,job實例的真正創建是由jobDetail完成的。job實例(WorkflowJob、LocalWorkflowJob或者是其他自定義擴展類)激活后,其excute方法將會被執行。其內部的執行邏輯大體是獲得指定的Workflow實例,然后執行該實例的executeTriggerFunction方法。trigger function的執行與先前在流程定義文件中所出現過的普通function大同小異。當然,我們還需要在流程定義文件中加入對trigger function的描述,大致格式如下:
<trigger-functions>
<trigger-function id="1″ >
<function>
…
</function>
</trigger-functions>
osworkflow解讀之四
流程啟動>>
前面分析流程流轉的執行邏輯時,并沒有講到流程的啟動。實際上,有了前面的基礎,再加上對流程定義文件加載的過程有清晰認識之后,流程啟動邏輯是很容易理解的。大致情況如下:
- 調用DefaultConfiguration的getWorkflow,傳入流程名稱,然后返回一個WorkflowDescriptor實例,流程定義文件的加載就是在這個時候完成的;
- 然后做一些準備工作,比如:獲取WorkflowStore實例、準備好transientVars和propertySet等等;
- 調用WorkflowDescriptor的getInitialAction方法,獲取initial action(如果有的話),注意此前的第一步中,流程定義文件已經成功加載;
- 調用transitionWorkflow,執行流程的流轉(就是前面提到doAction執行邏輯時調用的那個重要的方法);
在transitionWorkflow方法中,與流程啟動后step間的流轉稍有不同的是:當發現action為initial action時,將會把流程設置為activated狀態,表示流程啟動。就是下面這段代碼:
if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
}
WorkflowStore和WorkflowEntry>>
前面提到過,在AbstractWorkflow中,包含流程流轉關鍵邏輯的transitionWorkflow方法,會創建新的step。而這一創建工作是通過調用另一個member method實現的,也就是createNewCurrentStep。其執行邏輯大致如下:
int nextStep = theResult.getStep();
if (nextStep == -1) {
if (currentStep != null) {
nextStep = currentStep.getStepId();
}
}
…
if (currentStep != null) {
store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
store.moveToHistory(currentStep);
}
…
Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
從這段代碼中,我們首先可以看到,osworkflow可以支持step節點自身再次被“激活”的行為,也就是重復執行某個step。而另一方面,隨后的創建工作則是調用了store.createCurrentStep。
這個store變量就是一個實現了WorkflowStore接口的實例變量,缺省是MemoryWorkflowStore。WorkflowStore除了創建step之外,還提供了一系列find和query方法。在其內部分別保存著step的歷史記錄(history steps)以及當前處于“激活”狀態的step(current steps),以MemoryWorkflowStore為例,分別對應了兩個HashMap,而JDBCWorkflowStore則利用數據庫表來記錄這些信息。
實際上,WorkflowStore可以同時保存多個流程的記錄,這樣就可以滿足同時存在多個流程的情況。為此,osworkflow提供了一個WorkflowEntry接口用來描述流程信息,其中包含了流程名稱、流程ID、當前狀態等等。WorkflowEntry中定義了如下幾個流程狀態常量:
public static final int CREATED = 0; //創建
public static final int ACTIVATED = 1; // 激活
public static final int SUSPENDED = 2; // 掛起
public static final int KILLED = 3; // 異常終止
public static final int COMPLETED = 4; // 正常結束
public static final int UNKNOWN = -1;
在WorkflowStore提供的接口方法中有如下幾個方法供維護WorkflowEntry使用:
public WorkflowEntry createEntry(String workflowName) throws StoreException;
public WorkflowEntry findEntry(long entryId) throws StoreException;
public void setEntryState(long entryId, int state) throws StoreException;
具體的方法實現,各個具現類有所不同。比如。在MemoryWorkflowStore中,是通過維護一個存儲SimpleWorkflowEntry實例(實現了WorkflowEntry接口)的HashMap達到目的的。
OSWorkflow解讀之三
初始配置加載及工作流定義文件加載>>
AbstractWorkflow首先會取得一個Configuration實例,缺省時為DefaultConfiguration(實現了Configuration接口),該實例負責系統配置的加載。AbstractWorkflow會調用其load方法,該方法內部會查找一個名為osworkflow.xml的配置文件,并對其解析。osworkflow.xml文件中一般會指定persistence class和factory class,比如:
<osworkflow>
<persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>
<factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
<property key="resource” value="workflows.xml” />
</factory>
</osworkflow>
load方法會動態加載這些class,還可以指定一些參數,在它們初始化的時候傳入。在這里,我們指定了XMLWorkflowFactory,實際上還有其他種類的WorkflowFactory,比如JDBCWorkflowFactory、URLWorkflowFactory,它們均派生自AbstractWorkflowFactory,其共同職責是通過某種媒介加載流程定義。
以XMLWorkflowFactory為例,其相應實例在load方法內部完成初始化的過程中,將會查找一個名為workflows.xml的文件,可以在該文件中定義多個流程,每個流程指明其對應的xml定義文件。比如:
<workflows>
<workflow name="example” type="resource” location="example.xml"/>
</workflows>
在XMLWorkflowFactory內部維護了一個Map,保存著流程名稱與其對應的文件路徑(實際上是一個WorkflowConfig實例,不過這只是細節)。然后,DefaultConfiguration調用XMLWorkflowFactory.getWorkflow方法,并傳入流程名稱。
getWorkflow方法內部又會將流程加載的具體執行邏輯轉交給一個名為WorkflowLoader的類(調用WorkflowLoader.load方法),由WorkflowLoader實現流程定義文件讀取。不過,真正的xml文件解析是由WorkflowDescriptor類完成的。它將平面的xml流轉化為osworkflow內部所使用的具有真正意義的對象。此類有很多get方法,在osworkflow的許多地方都將會用到這個類的get方法,以獲取具體的對象,比如:getAction,getJoin,getStep等等。
最終,代表example.xml流程定義文件的WorkflowDescriptor實例將會被逐層返回,直至AbstractWorkflow。至此,流程定義文件加載完畢,整個初始化過程也就基本完成了。
OSWorkflow解讀之二
多種方式定制邏輯>>
osworkflow的幾個基本元件都具有很好的擴展性,它們分別是:condition,function,register,validator。以condition為例,我們可以為觸發action的condition定義任意復雜的邏輯,而這種邏輯可以包含在一個java class中,也可以采用bsf,或者bean shell,還有local ejb和remote ejb。只要流程定義文件做相應配置即可,以java class和bean shell為例:
<action id="1″ name="Start Workflow">
<restrict-to>
<conditions type="AND">
<condition type="beanshell">
<arg name="script">true</arg>
</condition>
<condition type="class">
<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
<arg name="group">foos</arg>
</condition>
</conditions>
</restrict-to>
…
</action>
在osworkflow的許多地方都可以見到類似如下的代碼(此處以function為例):
if ("remote-ejb".equals(type)) { clazz = RemoteEJBFunctionProvider.class.getName(); } else if ("local-ejb".equals(type)) { clazz = LocalEJBFunctionProvider.class.getName(); } else if ("jndi".equals(type)) { clazz = JNDIFunctionProvider.class.getName(); } else if ("bsf".equals(type)) { clazz = BSFFunctionProvider.class.getName(); } else if ("beanshell".equals(type)) { clazz = BeanShellFunctionProvider.class.getName(); } else { clazz = (String) args.get(CLASS_NAME); } FunctionProvider provider = (FunctionProvider) loadObject(clazz); provider.execute(transientVars, args, ps);
loadObject會動態加載相應的具現處理類(比如BSFFunctionProvider),并轉換為基類類型(比如FunctionProvider),然后調用相應的執行邏輯(比如provider.execute)。這一模式屢試不爽。
OSWorkflow解讀之一
AbstractWorkflow>>
osworkflow中有關工作流流轉的所有核心代碼都在AbstractWorkflow中,BasicWorkflow就是派生自它,不過這個BasicWorkflow基本上沒做什么事情。也許我們還可以從AbstractWorkflow派生自己的Workflow類以加入擴展功能,大概這也算是osworkflow所體現的一種靈活性了,即:允許對工作流流轉的執行邏輯進行修改。AbstractWorkflow實現了Workflow接口,該接口包含了有關工作流的核心方法,最重要的是doAction方法,AbstractWorkflow實現了該方法,后面會提及,其他還有一些getter和query method。
流程流轉的執行邏輯>>
當流程執行到的某個step時,可能有一個或多個action可供用戶選擇執行。一旦確定執行某個action后,我們需要調用AbstractWorkflow.doAction,并傳入流程id和action的id。以下是對doAction的執行邏輯的一個不太嚴緊的算法描述:
* * *
- 根據流程id,獲得所有當前的step,這種情況往往發生在有split的時候,此時會有多個step等待執行;
- 根據傳入的action的id,檢查是否是global action;
- 若不是global action,則遍歷所有當前的step,對每個step的每個action調用isActionAvailable方法,檢查該action是否可用(記住step和action是一對多的關系);
- 所謂可用是指,通過執行passesConditions,逐個檢查action的condition:若是OR的關系,則有一個condition為真即為可用,AND關系則類推;
- 若action可用,則調用transitionWorkflow,這是流程流轉處理的關鍵部分;
執行transitionWorkflow時:
- 首先獲取當前step,存在有多個當前step的情況,比如split,此時獲取首個isAvailableAction為真的step;
- 調用verifyInputs驗證輸入(如果action有validator的話);
- 執行當前step的post function(因為該step即將結束);
- 執行action的pre function;
- 判斷當前step所屬的result中的所有condition是否滿足要求,判斷方法類似action的condition;
- 一旦滿足,則獲取result的pre function和post function;
- 否則即是unconditional result,獲取相應的pre function和post function;
- 在沒有split和join的情況下
- 會根據在result中指定的下一個step的id,創建一個新的step,作為當前的step;
- 從current steps中移除原來的當前step,并添加到history steps中;
- 如果新的step有pre function,則會馬上執行;
- 執行result的post function;
- 執行action的post function;
- 若action是intial action,則將流程設置為activated狀態;
- 若action是finish action,則將流程設置為completed狀態,返回true;
- 尋找auto action,若有的話,則執行之,執行方法是調用doAction本身;
- 返回false;
- 根據transitionWorkflow的返回值判斷流程是否結束;
- 若返回false,則調用checkImplicitFinish檢查是否存在implicit finish,即:當前沒有一個step的action可用時,就認為流程應該結束;
* * *
- 若存在split,則會創建多個新的step,并且在創建之前先執行split的pre function,在創建之后執行split的post function;
- 創建step的過程和上面描述的普通狀況相同:維護好current steps和history steps,并執行新的step的pre function;
* * *
- 若存在join,先結束當前step,并將該step添加至history steps和join steps;
- 查找history steps,對每個已完成的step,查看是否在其result或unconditional result中有join一項,若有則加入join steps中;
- 檢查join是否已經滿足:可以使用Bean Shell,在xml定義文件的join節點中,通過引用一個名為“jn”的特殊變量來指定join的滿足條件,jn記錄了有關join的關鍵信息;
- 若條件滿足,則執行join的pre function,維護好history steps,并創建下一個step,然后執行join的post function;
* * *
- 對于條件循環的情況,可以通過將result的某個action的下一個step指定為自身來加以實現,這只是在xml定義文件中做文章,流程執行邏輯無需做特殊處理;
time granulrity,issue tracking,tools vs. human
time granulrity:
作為時間的一種度量單位,或小時記,或天記,都是靈活的和因人而異的。比如你完全可以將8個小時理解為1天。但是,粒度的確定并非沒有原則:為任務分配時間是一種預估,這樣的估計如果易變而不準確,那么多半也就失去了意義。一般而言,只有在對所做的工作十分了解的前提下,才能夠準確估計。對于一個易變的的環境,陌生的環境,時常被打斷的環境,或者時常冒出些“障礙”的環境(比如某個艱深的bug fix),時間的粒度就不亦過細,至少在迭代的初期是如此。隨著時間的推移和經驗的積累,估計的粒度也就會相應改進。
issue tracking
issue tracking至關重要,否則事態就可能滑入不可控的危險,team member就會失去對項目狀況的全景認識,這也是我的切身體會。實際上,不單是bug,feature,defact,……,很多東西都是需要有tracking的。比如某此會議中的一個idea,如果沒有某種形式的tracking,很容易就會消失在大家的記憶中,直到哪次再被大家帶著一種似曾相識的感情從被遺忘的角落里重新提起。
tools vs. human
我始終覺得,在一個開發團隊中,對于工具的使用,總是二等考量,更為重要的是人的因素。因為,即使有趁手的工具,也依然有可能存在缺乏交流的team member,以及由這些team member參加的冗長的缺乏主題的會議。
OSWorkflow開篇
剛剛下載了OSWorkflow,這里摘錄的是聯機文檔中的開篇部分,也就是OSWorkflow的核心概念了:
OSWorkflow is based heavily on the concept of the finite state machine. Each state is represented by the combination of a step ID and a status. A transition from one state to another cannot happen without an action occuring first. There are always at least one or more active states during the lifetime
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=447742
h3 定義條件和函數
你也許已經注意到,到目前為止,我們定義的條件和函數類型都是"class""""""""""""""""""""class.name"""""""""""FunctionProvider或Condition接口的完整類名。
在osworkflow里面也有一些其他內置的類型,包括beanshell,無狀態的session bean,JNDI樹上的函數等。我們在下面的例子里使用beanshell類型。
h3 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""""""""""""""""""""""""
h3 Transient Map 臨時變量
另外一個和propertyset變量相對的概念是臨時變量:"transientVars""""""""""""map,只是在當前的工作流調用的上下文內有效。它包括當前的工作流實例,工作流定義等對應值的引用。你可以通過FunctionProvider的javadoc來查看這個map有那些可用的key。
還記得我們在教程的第2部分傳入的那個null嗎?如果我們不傳入null的話,那么這些輸入數據將會被添加到臨時變量的map里。
h3 inputs 輸入
每次調用workflow的動作時可以輸入一個可選的map,可以在這個map里面包含供函數和條件使用的任何數據,它不會被持久化,只是一個簡單的數據傳遞。
h3 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) thrownew InvalidInputException("Missing working.title"); if(title.length() > 30) thrownew 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字符,將會讓這個動作成功執行。
我們已經介紹了輸入和校驗,下面來看看寄存器。
h3 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
h3 結論
這個教程的目的是希望可以闡明一些主要的osworkflow概念。你還可以通過API和流程定義格式去獲取更多的信息。有一些更高級的特性沒有在此提到,比如splits 分支、joins 連接, nested conditions 復合條件、auto stpes 自動步驟等等。你可以通過閱讀手冊來獲得更進一步的理解。
如果你遇到任何的困難,可以在osworkflow的email list上詢問。