要進行整合,可以使用的方式有三種
1. 自訂表單
2. WFCI
3. Web Services
第一種方式較為單純,就是提供一組 script 嵌入原本的 Web App。透過流程設定的自訂文件,在簽核時,將表單 redirect 到您的 Web App,同時將流程所使用的參數透過 Form 的包裹方式,Post到您的Web App,讓您的 Web App 可以使用,算起來是最為便利的,也是最快的方式,但是就受限於表單的資料定義項目以及所提供的 Web 參數,同時只能從WebAgenda去啟動流程,在整合度上相對受到限制。
第二種方式,是一組完整的API讓您的程式可以直接跟Agentflow(PASE Server)溝通,同時可以完全客製出您的Client端(相對於Agentflow Portal),但缺點是只支援Java Program(Java AP & JSP),如果要讓 .Net App使用,則必須透過 .Net Java Plug-In(iKVM.Net)將 jar 檔轉換成 dll 讓 .Net 使用。
第三種方式,則是Follow標準的Web Service作法,由Agentflow所提供的 Web Services來跟Agentflow溝通(設定方式詳見上一篇),走的是標準的SOAP協定,所以在效能上會大打折扣(當然,這得視您要傳輸的資料量大小有關)。
這邊有一個觀念,必須先釐清楚:以前在開發Notes Workflow AP時,常聽到的說法就是Notes的Workflow是屬於Form Based的流程引擎(Agentflow在流程設計上如果照字面來看,某種程度上也算是,因為一定需要提供一個表單,但是把它當作流程所需使用資訊的Table即可);也就是說,流程必須綁住表單,流程的資料都來自於表單(這種說法,基本上是對的),所以流程無法與表單抽離…這樣的說法基本上是一種謬誤!如果我們將流程需使用的表單,當做是流程資料的承載,將使用者實際面對的表單,當做是資料的展現,這樣我就可以將使用者表單與流程抽離,我的使用者表單負責的是要呈現給使用者的資訊,當我需要跑流程的時候,我再透過呼叫流程API的方式,將流程會使用到的資料寫入流程使用的表單,讓流程知道怎麼跑像下一關即可,我的使用者表單根本不需要管流程怎麼跑,這也是當初我拿Lotus Workflow當做流程引擎,用網頁來設計使用者表單的概念(這中間使用到一些AJAX的技巧,請搜尋一下本網誌 Domino主題)。
那到底要怎麼使用才會比較合適呢?如果我是.Net的開發者,是不是會很困難?不像Java 開發那樣擁有較多的Resource?
基本上,我是把Web Service先排除掉了,因為設定上比較麻煩,中間多走了一層Protocol,但是我如果完全要透過WFCI重寫,那麼我舊的AP又都沒辦法使用,等於是浪費人力與時間成本,但是使用自訂表單,又只能從WebAgenda去跑流程…該怎麼處理呢?
於是答案都在影片中….XD
前面我們提過,WFCI只支援Java/JSP,其實我需要的是由外部的程式啟動流程,然後使用者可以在WebAgenda中去簽核表單,也可以追蹤表單,也就是說由WenAgenda來幫我管理簽核流程。要達到這樣的功能,又不想重寫程式,那我們就需要透過自訂表單的協助,同時由WFCI來啟動流程…可是我的程式是 .Net開發的,怎麼去呼叫WFCI啊!
答案在JSP!(當然你也可以用Web Services)
我們可以寫一支JSP程式,專門用來讓外部程式啟動流程。(當然這是上課的教材Copy過來改的)
外部的程式只要將要啟動的流程ID,啟動流程的使用者,以及要傳給自訂表單的參數包裹成HTTP Form Data POST給 initProcess.jsp即可啟動流程。
底下,就為您示範如何在 Web App 中將 CustForm中所定義的資料欄位包裹起來,POST到 initProcess.jsp 透過 WFCI 來啟動 Agentflow 中的流程。
InitProcess.aspx.vb
- Imports System
- Imports System.Collections.Generic
- Imports System.Text
- Imports System.Net
- Imports System.Collections.Specialized
- Imports System.IO
- Partial Class Forms_HttpRpc_frmDCInitProcess
- Inherits System.Web.UI.Page
- Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
- Dim httpPost As HttpPostCallWebSite = New HttpPostCallWebSite()
- Dim keyValuePair As NameValueCollection = New NameValueCollection()
- keyValuePair.Add("processID", "PRO00431303438269342") ' 流程 ID
- keyValuePair.Add("userID", Request("wm_id")) ' 啟動流程的人員 ID
- keyValuePair.Add("pm_id", Request("pm_id")) ' CustForm 定義的表單欄位
- keyValuePair.Add("csr_id", Request("csr_id")) ' CustForm 定義的表單欄位
- keyValuePair.Add("wm_id", Request("wm_id")) ' CustForm 定義的表單欄位
- keyValuePair.Add("PB_NO", Request("PB_NO")) ' CustForm 定義的表單欄位
- Dim url As String = "http://localhost:8080/WebAgenda/initProcess.jsp"
- Dim result As String = httpPost.RequestHttpPost(url, keyValuePair)
- Response.Write(result)
- End Sub
- End Class
- ' POST data class
- Public Class HttpPostCallWebSite
- Inherits System.Web.UI.Page
- Public Function RequestHttpPost(ByVal webUrl As String, ByVal keyValuePair As NameValueCollection) As String
- Dim ret As String
- ' Create a request using a URL that can receive a post.
- Dim request As WebRequest = WebRequest.Create(webUrl)
- ' Set the Method property of the request to POST.
- request.Method = "POST"
- 'Set Http Post Params
- Dim strParams As String = ""
- Try
- Dim i As Integer
- For i = 0 To keyValuePair.Count - 1
- strParams += keyValuePair.GetKey(i) + "=" + keyValuePair.Get(i) + "&"
- Next i
- strParams = IIf(Right(strParams, 1).Equals("&"), Left(strParams, Len(strParams) - 1), strParams)
- ' Create POST data and convert it to a byte array.
- Dim byteArray As Byte() = Encoding.UTF8.GetBytes(strParams)
- ' Set the ContentType property of the WebRequest.
- request.ContentType = "application/x-www-form-urlencoded"
- ' Set the ContentLength property of the WebRequest.
- request.ContentLength = byteArray.Length
- ' Get the request stream.
- Dim dataStream As Stream = request.GetRequestStream()
- ' Write the data to the request stream.
- dataStream.Write(byteArray, 0, byteArray.Length)
- ' Close the Stream object.
- dataStream.Close()
- ' Get the response.
- Dim response As WebResponse = request.GetResponse()
- ' Display the status.
- ret = CType(response, HttpWebResponse).StatusDescription
- ' Get the stream containing content returned by the server.
- dataStream = response.GetResponseStream()
- ' Open the stream using a StreamReader for easy access.
- Dim reader As New StreamReader(dataStream)
- ' Read the content.
- Dim responseFromServer As String = reader.ReadToEnd()
- ' Clean up the streams.
- reader.Close()
- dataStream.Close()
- response.Close()
- Return ret & " " & responseFromServer
- Catch ex As Exception
- Return "Para:" & strParams & ex.Message
- End Try
- End Function
- End Class
上面這支AP就是我們外部程式,透過 WebRequest 這個Class來呼叫啟動流程的 jsp 程式。接下來,我們就來看 initProcess.jsp 該如何撰寫。
initProcess.jsp
- <%@ page language="java" contentType="text/html;charset=UTF-8" %>
- <%@ page import="java.io.*,java.util.*,
- java.util.HashMap,
- java.util.Vector,
- si.wfinterface.WFCI,
- si.wfcidata.WFCIException,
- si.WFCIFactory,
- pe.pase.MemberRecord,
- pe.pase.Role,
- pe.pase.DBProcess,
- pe.pase.Task,
- pe.pase.PASEartInstance" %>
- <%
- String PASE_IP = "localhost";
- String PASE_PORT = "1099";
- WFCI wfci = null;
- String processID="";
- String userID="";
- //wfci = WebSystem.getWFCI();
- //if (wfci==null) {
- try {
- wfci = WFCIFactory.createWFCIRMIImpl();
- if (wfci.connectServer(PASE_IP, PASE_PORT) == -1) {
- throw new Exception("can't connect Flow Server: host=" + PASE_IP + " Port=" + PASE_PORT);
- }
- HashMap dataMap = new HashMap(); //傳送資料
- // 接收傳進來的 Form data
- Enumeration paramNames = request.getParameterNames();
- while(paramNames.hasMoreElements()) {
- String paramName = (String)paramNames.nextElement();
- out.print("" + paramName + "\n");
- String paramValue = request.getParameter(paramName);
- out.println(" " + paramValue + "\n");
- if ("processID".equals(paramName)){
- processID=paramValue;
- } else if ("userID".equals(paramName)){
- userID=paramValue;
- } else {
- dataMap.put(paramName,paramValue);
- }
- }
- //取得人員相關資料
- MemberRecord MR = wfci.getMember(userID);
- if (MR != null){
- String userName = MR.getName();
- //取得人員職務相關資料
- String roleID = MR.getMainRoleID();
- Role userRole = wfci.getRole(roleID);
- String roleName = userRole.getName();
- out.println("createProcess from UserName:"+userName+"useRoleName:"+roleName);
- //取得流程相關資料
- DBProcess DBp = wfci.getDBProcess(processID);
- if (DBp != null){
- out.println("createPrcoessName :"+DBp.getName());
- }
- //createProcess return RootTask
- String taskID = wfci.createProcess(userID,processID,dataMap,false);
- if (taskID != null){
- out.println("createProcess success!!");
- Task rootTask = wfci.getTask(taskID);
- out.println("rootTask Type is :"+rootTask.getTaskType());
- String rootProcessID = rootTask.getProcessID();
- DBProcess rootProcess = wfci.getDBProcess(rootProcessID);
- out.println("rootTask proceeName:"+rootProcess.getName());
- }
- }else{
- out.println("can't get "+userID+" MemberRecoed");
- }
- }catch(WFCIException e){
- out.println("catch wfci throws Exception : "+e);
- e.printStackTrace();
- }catch(Exception e){
- out.println("catch Exception : "+e);
- e.printStackTrace();
- }finally{
- //must disconnect
- if (wfci != null)
- wfci.disconnectServer();
- }
- //}
- %>
粗體字的部份,就是呼叫 WFCI 啟動流程的重點,Form data 的接收,透過
Enumeration paramNames = request.getParameterNames();
可以將資料一個一個取出來,再組成 HashMap丟到流程裡。
這樣我們就把流程啟動了!(得意)
噫?沒動靜!
對!少了一個步驟,流程必須知道你送過來的資料是什麼,才能夠把資料存到 CustForm 定義的資料欄位裡面!
所以我們在第一個關卡的 PreAction 這個 Script 區塊中,要加上底下這一段程式碼
- var hm = Server.getGlobals(MyTask.getRootID());
- var Artins = MyTask.getArtInstance();
- var pm_id = hm.get("pm_id");
- var csr_id = hm.get("csr_id");
- var wm_id = hm.get("wm_id");
- var PB_NO = hm.get("PB_NO");
- Artins.setAppValue("pm_id",pm_id);
- Artins.setAppValue("csr_id",csr_id);
- Artins.setAppValue("wm_id",wm_id);
- Artins.setAppValue("PB_NO",PB_NO);
- //Artins.setAppValue("pm_id",hm.get("pm_id"));
- java.lang.System.out.println("======== pm_id:"+pm_id);
- java.lang.System.out.println("======== csr_id:"+csr_id);
- java.lang.System.out.println("======== wm_id:"+wm_id);
- java.lang.System.out.println("======== PB_NO:"+PB_NO);
這樣就能夠啟動流程並與舊有的 AP 結合使用了!(請參考 AF3-4 講義中的作法,底下的程式碼,因為顯示的問題所以加上了一些空格讓程式碼可以顯示完整)
- < script src="<%=Request("eform.web.url") %>/eform/customform.js" type="text/javascript" >< /script >
- < form id="eForm" action="<%=Request("eform.web.url") %>/custForm.do" method="post" >
- < input name="eform.method" type="hidden" value="suspend" / >
- < input name="eform.taskId" type="hidden" value="<%=Request("eform.taskId")%>" / >
- < input name="pb_no" type="hidden" value="<%=Request("pb_no")%>" / >
- < /form >
7 意見
請教後來怎選Agentflow不用Lotus主要原因是?
Reply主要是平台過舊,email Client 不如 Outlook 好用
Reply在上面開發流程與表單,要整合並不容易
不想在花錢投資在這上頭
James, 請問您熟悉AgentFlow嗎?
ReplyJames, 可以請教您一些AgentFlow的問題嗎?
ReplyJames, 可以跟您請教一些AgentFlow的問題嗎?
ReplyHi, 瑋叡
Reply您可以發 mail 到我的信箱
jamesjantw@gmail.com
歡迎一起討論
您好..
Reply拜讀您的這篇文章,有實際操作一次,我是用.NET寫的自訂表單,放到AGENTFLOW後,沒辦法像一般表單可以跑流程,不知是否還有其他地方應注意呢?
張貼留言