{-# OPTIONS -XDeriveDataTypeable #-}
{-
This program is an example of simple workflow management. Once a document
is created by the user, a workflow controls two levels of approbal (boss and superboss) trough
messages to the presentation layer of the three different users.
A document is created by the user, then is validated by the boss and the super boss.
If any of the two dissapprobe, the document is sent to the user to modify it.
This program can handle as many document workflows as you like simultaneously.
this is a version with more transaction-aware communications between the workflow and
the user interfaces. Most of te Workflow and communication primitives are used.
The second level of approbal now has a timeout∘The seralization of the document is
trough the Serialize class of the RefSerialize package.
There is also a rudimentary account of document modifications
When te document title is modified, the workflow launches a new workflow with the new
document and stops.
-}
import Control.Workflow
import Data.TCache.IDynamic
import Data.Typeable
import System.Exit
import Data.List (find,(\))
import Data.Maybe(fromJust)
import Control.Monad (when)
import Control.Concurrent ( forkIO,threadDelay)
import GHC.Conc( atomically, unsafeIOToSTM, STM, orElse)
import Data.RefSerialize
import Data.TCache.Dynamic
import Debug.Trace
debug a b= trace b a
data Document=Document{title :: String , text :: [String]} deriving (Read, Show,Eq,Typeable)
instance IResource Document where
keyResource (Document t _)= t
tshowp (Document title text)= do
title1 ← showp title
stext ← rshowp text
return $ "Document " ⊕ title1 ⊕ stext
treadp= do
symbol "Document"
title ← readp
text ← rreadp
return $ Document title text
docWorkflows=[("docApprobal",docApprobal)]
main= do
-- register all the data types to be returned in the workflow steps
registerType :: IO Document
registerType :: IO ()
registerType :: IO Bool
-- restart the interrupted workflows
restartWorkflows docWorkflows
putStrLn "nThis program is an example of simple workflow management; once a document is created a workflow thread controls the flow o mail messages to three different users that approbe or disapprobe and modify the document"
putStrLn "A document is created by the user, then is validated by the boss and the super boss. If any of the two dissapprobe, the document is sent to the user to modify it."
putStrLn "n please login as:λn 1- userλn 2- bossλn 3- super bossλnλn Enter the number"
n ← getLine
case n of
"1" → userMenu
"2" → aprobal boss
"3" → aprobal superboss
-- The workflow.
-- Think on it as a persistent thread
docApprobal :: Document → Workflow IO ()
docApprobal doc= do
logWF "send a message to the boss requesting approbal"
step $ writeQueue boss doc
-- wait for any respoinse from the boss
let docQueue= receiver approbal doc
ap ← step $ readQueue docQueue
case ap of
False → logWF "¬ approbed, sent to the user for correction" >> correctWF doc
True → do
logWF " approbed, send a message to the superboss requesting approbal"
step $ writeQueue superboss doc
-- wait for any respoinse from the superboss
-- if no response from the superboss in 5 minutes, it is validated
flag ← getTimeoutFlag $ 5 * 60
ap ← step ∘ atomically $ readQueueSTM docQueue `orElse` waitUntilSTM flag >> return True
case ap of
False → logWF "¬ approbed, sent to the user for correction" >> correctWF doc
True → do
logWF " approbed, sent to the list of approbed documents"
step $ writeQueue approbed doc
correctWF :: Document → Workflow IO ()
correctWF doc= do
step $ writeQueue user doc -- send a message to the user to correct the document
-- wait for the document approbal
doc' ← step $ readQueue (title doc)
if title doc ≠ title doc'
-- if doc and new doc hace different document title, then start a new workflow for this new document
-- since a workflow is identified by the workflow name and the key of the starting data, this is a convenient thing.
then step $ startWF_ "docApprobal" doc' docWorkflows
-- else continue the current workflow
else docApprobal doc'
create = do
separator
doc ← readDoc
putStrLn "The document has been sent to the boss.nPlease wait for the approbal"
forkIO $ startWF_ "docApprobal" doc docWorkflows
userMenu
{-
finaldoc ← startWF "docApprobal" doc docWorkflows
Just sequenceAprobal ← getWFHistory "docApprobal" doc
printHistory sequenceAprobal
-}
user= "user"
boss = "boss"
superboss= "superboss"
approbed = "approbed"
approbal= "approbal"
userMenu= do
separator
putStrLn"nλn1- Create documentλn2- Documents to modifyλn3- Approbed documentsλn4- manage workflowsλn5- exit"
n ← getLine
case n of
"1" → create
"2" → modify
"3" → view
"4" → history
"5" → exitSuccess
userMenu
handle = flip catch
history= do
separator
putStr "MANAGE WORKFLOWSλn"
ks ← getWFKeys "docApprobal"
mapM (λ(n,d) → putStr (show n) >> putStr "- " >> putStrLn d) $ zip [1..] ks
putStr $ show $ length ks + 1
putStrLn "- back"
putStrLn ""
putStrLn " select v <number> to view the history or d <number> to delete it"
l ← getLine
let n= read $ drop 2 l
let docproto= Document{title= ks !! (n-1), text=undefined}
case head l of
'v' → do
getWFHistory "docApprobal" docproto ↠ printHistory ∘ fromJust
history
'd' → do
delWFHistory "docApprobal" docproto
history
_ → history
separator= putStrLn "------------------------------------------------"
modify :: IO ()
modify= do
separator
empty ← isEmptyQueue user :: IO Bool
if empty then putStrLn "thanks, enter as Boss for the approbal"else do
doc ← atomically $ do
doc ← readQueueSTM user
unreadQueueSTM user doc
return doc
putStrLn "Please correct this doc"
print doc
doc1 ← readDoc
return $ diff doc1 doc
atomically $ do
readQueueSTM user :: STM Document
writeQueueSTM (title doc) doc1
modify
diff (Document t xs) (Document _ ys)= Document t $ map (search ys) xs where
search xs x= case find (≡x) xs of
Just x' → x'
Nothing → x
readDoc :: IO Document
readDoc = do
putStrLn "please enter the title of the document"
title1 ← getLine
h ← getWFHistory "docApprobal" $ Document title1 undefined
case h of
Just _ → putStrLn "sorry document title already existent, try other" >> readDoc
Nothing → do
putStrLn "please enter the text. "
putStrLn "the edition will end wth a empty line "
text ← readDoc1 [title1]
return $ Document title1 text
where
readDoc1 text= do
line ← getLine
if line ≡ "" then return text else readDoc1 $ text ⊕ [line]
receiver name doc= name⊕keyResource doc
view= do
separator
putStrLn "LIST OF APPROBED DOCUMENTS:"
view1
where
view1= do
empty ← isEmptyQueue approbed
if empty then return () else do
doc ← readQueue approbed :: IO Document
print doc
view1
aprobal who= do
separator
aprobalList
putStrLn $ "thanks , press any key to exit, "++ who
getLine
return ()
where
aprobalList= do
empty ← isEmptyQueue who
if empty
then do
putStrLn "No more document to validate. Bye"
return ()
else do
doc ← atomically $do
doc ← readQueueSTM who
unreadQueueSTM who doc
return doc
syncCache
approbal1 doc
aprobalList
approbal1 :: Document → IO ()
approbal1 doc= do
putStrLn $ "hi " ⊕ who ++", a new request for aprobal has arrived:"
print doc
putStrLn $ "Would you approbe this document? s/n"
l ← getLine
let b= head l
let res= if b ≡ 's' then True else False
-- send the message to the workflow
atomically $ do
empty ← isEmptyQueueSTM who
readQueueSTM who :: STM Document
writeQueueSTM (receiver approbal doc) res
syncCache
Add a code snippet to your website: www.paste.org