-- -- acs-workflow/sql/workflow-case-package.sql -- -- Creates the PL/SQL package that provides the API for interacting -- with a workflow case. -- -- @author Lars Pind (lars@pinds.com) -- -- @creation-date 2000-05-18 -- -- @cvs-id $Id: workflow-case-package.sql,v 1.1 2001/04/09 23:13:49 danw Exp $ -- -- create or replace package workflow_case -- is -- -- function new ( -- case_id in number default null, -- workflow_key in varchar2, -- context_key in varchar2 default null, -- object_id in integer, -- creation_date in date default sysdate, -- creation_user in integer default null, -- creation_ip in varchar2 default null -- ) return integer; -- -- procedure add_manual_assignment ( -- case_id in number, -- transition_key in varchar2, -- party_id in number -- ); -- -- procedure remove_manual_assignment ( -- case_id in number, -- transition_key in varchar2, -- party_id in number -- ); -- -- procedure clear_manual_assignments ( -- case_id in number, -- transition_key in varchar2 -- ); -- -- procedure start_case ( -- case_id in number, -- creation_user in integer default null, -- creation_ip in varchar2 default null, -- msg in varchar2 default null -- ); -- -- procedure delete( -- case_id in number -- ); -- -- procedure suspend( -- case_id in number, -- user_id in number default null, -- ip_address in varchar2 default null, -- msg in varchar2 default null -- ); -- -- procedure resume( -- case_id in number, -- user_id in number default null, -- ip_address in varchar2 default null, -- msg in varchar2 default null -- ); -- -- procedure cancel( -- case_id in number, -- user_id in number default null, -- ip_address in varchar2 default null, -- msg in varchar2 default null -- ); -- -- procedure fire_message_transition ( -- task_id in number -- ); -- -- /* To perform an action on the workflow: -- * (numbers in parenthesis is the number of times each function should get called) -- * -- * 1. begin_task_action (1) -- * 2. set_attribute_value (0..*) -- * 3. clear_manual_assignments (0..1) -- * 4. add_manual_assignment (0..*) -- * 5. end_task_action (1) -- */ -- function begin_task_action ( -- task_id in number, -- action in varchar2, -- action_ip in varchar2, -- user_id in number, -- msg in varchar2 default null -- ) return number; -- -- procedure set_attribute_value ( -- journal_id in number, -- attribute_name in varchar2, -- value in varchar2 -- ); -- -- procedure end_task_action ( -- journal_id in number, -- action in varchar2, -- task_id in number -- ); -- -- /* Shortcut, that does both begin and end, when you have no attributes to set or assignments to make */ -- function task_action ( -- task_id in number, -- action in varchar2, -- action_ip in varchar2, -- user_id in number, -- msg in varchar2 default null -- ) return number; -- -- function get_attribute_value ( -- case_id in number, -- attribute_name in varchar2 -- ) return varchar2; -- -- procedure add_task_assignment ( -- task_id in number, -- party_id in number -- ); -- -- procedure remove_task_assignment ( -- task_id in number, -- party_id in number -- ); -- -- procedure clear_task_assignments ( -- task_id in number -- ); -- -- -- /* DBMS_JOBS */ -- -- procedure sweep_timed_transitions; -- -- procedure sweep_hold_timeout; -- -- end workflow_case; -- show errors -- create or replace package body workflow_case -- function new create function workflow_case__new (number,varchar,varchar,integer,timestamp,integer,varchar) returns integer as ' declare case_id alias for $1; workflow_key alias for $2; context_key alias for $3; object_id alias for $4; creation_date alias for $5; creation_user alias for $6; creation_ip alias for $7; begin if context_key is null then v_context_key_for_query := ''default''; else v_context_key_for_query := context_key; end if; /* insert a row into acs_objects */ v_case_id := acs_object.new( object_id => new__case_id, object_type => new__workflow_key, creation_date => new__creation_date, creation_user => new__creation_user, creation_ip => new__creation_ip ); /* insert the case in to the general wf_cases table */ insert into wf_cases (case_id, workflow_key, context_key, object_id, state) values (v_case_id, new__workflow_key, v_context_key_for_query, new__object_id, ''created''); /* insert the case into the workflow-specific cases table */ select table_name into v_workflow_case_table from acs_object_types where object_type = new__workflow_key; execute ''insert into ''||v_workflow_case_table||'' (case_id) values (:1)'' using v_case_id; return v_case_id; end;' language 'plpgsql'; -- procedure add_manual_assignment create function workflow_case__add_manual_assignment (number,varchar,number) returns integer as ' declare case_id alias for $1; transition_key alias for $2; party_id alias for $3; v_workflow_key varchar(100); begin select workflow_key into v_workflow_key from wf_cases where case_id = add_manual_assignment__case_id; insert into wf_case_assignments (case_id, workflow_key, transition_key, party_id) values (add_manual_assignment__case_id, v_workflow_key, add_manual_assignment__transition_key, add_manual_assignment__party_id); return 0; end;' language 'plpgsql'; -- procedure remove_manual_assignment create function workflow_case__remove_manual_assignment (number,varchar,number) returns integer as ' declare case_id alias for $1; transition_key alias for $2; party_id alias for $3; v_workflow_key varchar(100); begin select workflow_key into v_workflow_key from wf_cases where case_id = remove_manual_assignment__case_id; delete from wf_case_assignments where workflow_key = v_workflow_key and case_id = remove_manual_assignment__case_id and transition_key = remove_manual_assignment__transition_key and party_id = remove_manual_assignment__party_id; return 0; end;' language 'plpgsql'; -- procedure clear_manual_assignments create function workflow_case__clear_manual_assignments (number,varchar) returns integer as ' declare case_id alias for $1; transition_key alias for $2; v_workflow_key varchar(100); begin select workflow_key into v_workflow_key from wf_cases where case_id = clear_manual_assignments__case_id; delete from wf_case_assignments where workflow_key = v_workflow_key and case_id = clear_manual_assignments__case_id and transition_key = clear_manual_assignments__transition_key; return 0; end;' language 'plpgsql'; -- procedure start_case create function workflow_case__start_case (number,integer,varchar,varchar) returns integer as ' declare case_id alias for $1; creation_user alias for $2; creation_ip alias for $3; msg alias for $4; v_journal_id number; begin /* Add an entry to the journal */ v_journal_id := journal_entry.new( object_id => start_case__case_id, action => ''case start'', action_pretty => ''Case started'', creation_user => start_case__creation_user, creation_ip => start_case__creation_ip, msg => start_case__msg ); update wf_cases set state = ''active'' where case_id = start_case__case_id; add_token( case_id => start_case__case_id, place_key => ''start'', journal_id => v_journal_id ); /* Turn the wheels */ sweep_automatic_transitions( case_id => start_case__case_id, journal_id => v_journal_id ); return 0; end;' language 'plpgsql'; -- procedure delete create function workflow_case__delete (number) returns integer as ' declare case_id alias for $1; v_workflow_case_table varchar(30); begin /* delete attribute_value_audit, tokens, tasks */ delete from wf_attribute_value_audit where case_id = workflow_case.delete__case_id; delete from wf_case_assignments where case_id = workflow_case.delete__case_id; delete from wf_case_deadlines where case_id = workflow_case.delete__case_id; delete from wf_tokens where case_id = workflow_case.delete__case_id; delete from wf_task_assignments where task_id in (select task_id from wf_tasks where case_id = workflow_case.delete__case_id); delete from wf_tasks where case_id = workflow_case.delete__case_id; /* delete the journal */ journal_entry.delete_for_object(workflow_case.delete__case_id); /* delete from the workflow-specific cases table */ select table_name into v_workflow_case_table from acs_object_types ot, wf_cases c where c.case_id = workflow_case.delete__case_id and object_type = c.workflow_key; execute ''delete from ''||v_workflow_case_table||'' where case_id = :case_id'' using in workflow_case.delete__case_id; /* delete from the generic cases table */ delete from wf_cases where case_id = workflow_case.delete__case_id; /* delete from acs-objects */ acs_object.delete(workflow_case.delete__case_id); return 0; end;' language 'plpgsql'; -- procedure suspend create function workflow_case__suspend (number,number,varchar,varchar) returns integer as ' declare case_id alias for $1; user_id alias for $2; ip_address alias for $3; msg alias for $4; v_state varchar(40); v_journal_id number; begin select state into v_state from wf_cases where case_id = suspend__case_id; if v_state != ''active'' then raise_application_error(-20000, ''Only active cases can be suspended''); end if; /* Add an entry to the journal */ v_journal_id := journal_entry.new( object_id => suspend__case_id, action => ''case suspend'', action_pretty => ''case suspended'', creation_user => suspend__user_id, creation_ip => suspend__ip_address, msg => suspend__msg ); update wf_cases set state = ''suspended'' where case_id = suspend__case_id; return 0; end;' language 'plpgsql'; -- procedure resume create function workflow_case__resume (number,number,varchar,varchar) returns integer as ' declare case_id alias for $1; user_id alias for $2; ip_address alias for $3; msg alias for $4; v_state varchar(40); v_journal_id number; begin select state into v_state from wf_cases where case_id = resume__case_id; if v_state != ''suspended'' and v_state != ''canceled'' then raise_application_error(-20000, ''Only suspended or canceled cases can be resumed''); end if; /* Add an entry to the journal */ v_journal_id := journal_entry.new( object_id => resume__case_id, action => ''case resume'', action_pretty => ''case resumed'', creation_user => resume__user_id, creation_ip => resume__ip_address, msg => resume__msg ); update wf_cases set state = ''active'' where case_id = resume__case_id; return 0; end;' language 'plpgsql'; -- procedure cancel create function workflow_case__cancel (number,number,varchar,varchar) returns integer as ' declare case_id alias for $1; user_id alias for $2; ip_address alias for $3; msg alias for $4; v_state varchar(40); v_journal_id number; begin select state into v_state from wf_cases where case_id = cancel__case_id; if v_state != ''active'' and v_state != ''suspended'' then raise_application_error(-20000, ''Only active or suspended cases can be canceled''); end if; /* Add an entry to the journal */ v_journal_id := journal_entry.new( object_id => cancel__case_id, action => ''case cancel'', action_pretty => ''Case canceled'', creation_user => cancel__user_id, creation_ip => cancel__ip_address, msg => cancel__msg ); update wf_cases set state = ''canceled'' where case_id = cancel__case_id; return 0; end;' language 'plpgsql'; -- procedure fire_message_transition create function workflow_case__fire_message_transition (number) returns integer as ' declare task_id alias for $1; v_case_id number; v_transition_name varchar(100); v_trigger_type varchar(40); v_journal_id number; begin select t.case_id, tr.transition_name, tr.trigger_type into v_case_id, v_transition_name, v_trigger_type from wf_tasks t, wf_transitions tr where t.task_id = fire_message_transition__task_id and tr.workflow_key = t.workflow_key and tr.transition_key = t.transition_key; if v_trigger_type != ''message'' then raise_application_error(-20000, ''Transition ''''''||v_transition_name||'''''' is not message triggered''); end if; /* Add an entry to the journal */ v_journal_id := journal_entry.new( object_id => v_case_id, action => ''task ''||fire_message_transition__task_id||'' fire'', action_pretty => v_transition_name || '' fired'' ); fire_transition_internal( task_id => fire_message_transition__task_id, journal_id => v_journal_id ); sweep_automatic_transitions( case_id => v_case_id, journal_id => v_journal_id ); return 0; end;' language 'plpgsql'; -- function begin_task_action create function workflow_case__begin_task_action (number,varchar,varchar,number,varchar) returns integer as ' declare task_id alias for $1; action alias for $2; action_ip alias for $3; user_id alias for $4; msg alias for $5; begin select state into v_state from wf_tasks where task_id = begin_task_action__task_id; if begin_task_action__action = ''start'' then if v_state != ''enabled'' then raise_application_error(-20000, ''Task is in state ''''''||v_state||'''''', ''|| ''but it must be in state ''''enabled'''' to be started.''); end if; select decode(count(*),0,0,1) into v_num_rows from wf_user_tasks where task_id = begin_task_action__task_id and user_id = begin_task_action__user_id; if v_num_rows = 0 then raise_application_error(-20000, ''You are not assigned to this task.''); end if; elsif begin_task_action__action = ''finish'' or begin_task_action__action = ''cancel'' then if v_state = ''started'' then /* Is this user the holding user? */ select decode(count(*),0,0,1) into v_num_rows from wf_tasks where task_id = begin_task_action__task_id and holding_user = begin_task_action__user_id; if v_num_rows = 0 then raise_application_error(-20000, ''You are the user currently working on this task.''); end if; elsif v_state = ''enabled'' then if begin_task_action__action = ''cancel'' then raise_application_error(-20000, ''You can only cancel a task in state ''''started'''', ''|| ''but this task is in state ''''''||v_state||''''''''); end if; /* Is this user assigned to this task? */ select decode(count(*),0,0,1) into v_num_rows from wf_user_tasks where task_id = begin_task_action__task_id and user_id = begin_task_action__user_id; if v_num_rows = 0 then raise_application_error(-20000, ''You are not assigned to this task.''); end if; /* This task is finished without an explicit start. * Store the user as the holding_user */ update wf_tasks set holding_user = begin_task_action__user_id where task_id = begin_task_action__task_id; else raise_application_error(-20000, ''Task is in state ''''''||v_state||'''''', ''|| ''but it must be in state ''''enabled'''' or ''''started'''' to be finished''); end if; elsif begin_task_action__action = ''comment'' then -- We currently allow anyone to comment on a task -- (need this line because PL/SQL doens''t like empty if blocks) v_num_rows := 0; end if; select t.case_id, tr.transition_name into v_case_id, v_transition_name from wf_tasks t, wf_transitions tr where t.task_id = begin_task_action__task_id and tr.workflow_key = t.workflow_key and tr.transition_key = t.transition_key; /* Insert a journal entry */ v_journal_id := journal_entry.new( object_id => v_case_id, action => ''task ''||begin_task_action__task_id||'' ''||begin_task_action__action, action_pretty => v_transition_name || '' '' || begin_task_action__action, creation_user => begin_task_action__user_id, creation_ip => begin_task_action__action_ip, msg => begin_task_action__msg ); return v_journal_id; end;' language 'plpgsql'; -- procedure end_task_action create function workflow_case__end_task_action (number,varchar,number) returns integer as ' declare journal_id alias for $1; action alias for $2; task_id alias for $3; begin select creation_user into v_user_id from acs_objects where object_id = end_task_action__journal_id; /* Update the workflow state */ if end_task_action__action = ''start'' then start_task(end_task_action__task_id, v_user_id, end_task_action__journal_id); elsif end_task_action__action = ''finish'' then finish_task(end_task_action__task_id, end_task_action__journal_id); elsif end_task_action__action = ''cancel'' then cancel_task(end_task_action__task_id, end_task_action__journal_id); elsif end_task_action__action != ''comment'' then raise_application_error(-20000, ''Unknown action '''''' || end_task_action__action || ''''''''); end if; return 0; end;' language 'plpgsql'; -- function task_action create function workflow_case__task_action (number,varchar,varchar,number,varchar) returns number as ' declare task_id alias for $1; action alias for $2; action_ip alias for $3; user_id alias for $4; msg alias for $5; v_journal_id integer; begin v_journal_id := begin_task_action( task_id => task_action__task_id, action => task_action__action, action_ip => task_action__action_ip, user_id => task_action__user_id, msg => task_action__msg ); end_task_action( journal_id => v_journal_id, action => task_action__action, task_id => task_action__task_id ); return v_journal_id; end;' language 'plpgsql'; -- procedure set_attribute_value create function workflow_case__set_attribute_value (number,varchar,varchar) returns integer as ' declare journal_id alias for $1; attribute_name alias for $2; value alias for $3; begin select o.object_type, o.object_id into v_workflow_key, v_case_id from journal_entries je, acs_objects o where je.journal_id = set_attribute_value__journal_id and o.object_id = je.object_id; select attribute_id into v_attribute_id from acs_attributes where object_type = v_workflow_key and attribute_name = set_attribute_value__attribute_name; acs_object.set_attribute( object_id_in => v_case_id, attribute_name_in => set_attribute_value__attribute_name, value_in => set_attribute_value__value ); insert into wf_attribute_value_audit (case_id, attribute_id, journal_id, attr_value) values (v_case_id, v_attribute_id, set_attribute_value__journal_id, set_attribute_value__value); return 0; end;' language 'plpgsql'; -- function get_attribute_value create function workflow_case__get_attribute_value (number,varchar) returns integer as ' declare case_id alias for $1; attribute_name alias for $2; begin return acs_object.get_attribute( object_id_in => get_attribute_value__case_id, attribute_name_in => get_attribute_value__attribute_name ); end;' language 'plpgsql'; -- procedure add_task_assignment create function workflow_case__add_task_assignment (number,number) returns integer as ' declare task_id alias for $1; party_id alias for $2; v_count integer; v_workflow_key wf_workflows.workflow_key%TYPE; v_context_key wf_contexts.context_key%TYPE; v_case_id wf_cases.case_id%TYPE; v_transition_key wf_transitions.transition_key%TYPE; v_notification_callback wf_context_transition_info.notification_callback%TYPE; v_notification_custom_arg wf_context_transition_info.notification_custom_arg%TYPE; -- might cursor c_new_assigned_users select distinct from users where u.user_id select distinct from wf_task_assignments party_approved_member_map m2, users u2 where tasgn2.task_id and m2.party_id and u2.user_id and exists select 1 from party_approved_member_map where m.member_id and m.party_id ); cursor c_callback select notification_callback, notification_custom_arg from wf_context_transition_info where context_key and workflow_key and transition_key callback_rec c_callback%ROWTYPE; begin -- check that we don''t hit the unique constraint select count(*) into v_count from wf_task_assignments where task_id = add_task_assignment__task_id and party_id = add_task_assignment__party_id; if v_count > 0 then return; end if; -- get the notification callback info select case_id, workflow_key, transition_key into v_case_id, v_workflow_key, v_transition_key from wf_tasks where task_id = add_task_assignment__task_id; select context_key into v_context_key from wf_cases where case_id = v_case_id; open c_callback; fetch c_callback into callback_rec; if c_callback%FOUND then v_notification_callback := callback_rec.notification_callback; v_notification_custom_arg := callback_rec.notification_custom_arg; else v_notification_callback := null; v_notification_custom_arg := null; end if; -- notify any new assignees for v_assigned_user in c_new_assigned_users loop notify_assignee( task_id => add_task_assignment__task_id, user_id => v_assigned_user.user_id, callback => v_notification_callback, custom_arg => v_notification_custom_arg ); end loop; -- do the insert insert into wf_task_assignments (task_id, party_id) values (add_task_assignment__task_id, add_task_assignment__party_id); return 0; end;' language 'plpgsql'; -- procedure remove_task_assignment create function workflow_case__remove_task_assignment (number,number) returns integer as ' declare task_id alias for $1; party_id alias for $2; v_num_assigned number; v_case_id number; v_workflow_key varchar(100); v_transition_key varchar(100); v_context_key varchar(100); cursor c_callback select unassigned_callback, from wf_context_transition_info where workflow_key and context_key and transition_key callback_rec c_callback%ROWTYPE; begin delete from wf_task_assignments where task_id = remove_task_assignment__task_id and party_id = remove_task_assignment__party_id; select count(*) into v_num_assigned from wf_task_assignments where task_id = remove_task_assignment__task_id; if v_num_assigned > 0 then return; end if; select case_id, transition_key into v_case_id, v_transition_key from wf_tasks where task_id = remove_task_assignment__task_id; select workflow_key, context_key into v_workflow_key, v_context_key from wf_cases where case_id = v_case_id; open c_callback; fetch c_callback into callback_rec; if c_callback%FOUND then execute_unassigned_callback ( callback => callback_rec.unassigned_callback, task_id => task_id, custom_arg => callback_rec.unassigned_custom_arg ); end if; close c_callback; return 0; end;' language 'plpgsql'; -- procedure clear_task_assignments create function workflow_case__clear_task_assignments (number) returns integer as ' declare task_id alias for $1; v_case_id number; v_transition_key varchar(100); v_workflow_key varchar(100); v_context_key varchar(100); v_callback varchar(100); v_custom_arg varchar(4000); begin delete from wf_task_assignments where task_id = clear_task_assignments__task_id; select case_id, transition_key into v_case_id, v_transition_key from wf_tasks where task_id = clear_task_assignments__task_id; select workflow_key, context_key into v_workflow_key, v_context_key from wf_cases where case_id = v_case_id; select unassigned_callback, unassigned_custom_arg into v_callback, v_custom_arg from wf_context_transition_info where workflow_key = v_workflow_key and context_key = v_context_key and transition_key = v_transition_key; execute_unassigned_callback ( callback => v_callback, task_id => task_id, custom_arg => v_custom_arg ); return 0; end;' language 'plpgsql'; -- function evaluate_guard create function workflow_case__evaluate_guard (varchar,varchar,number,varchar,varchar,varchar,varchar) returns integer as ' declare callback alias for $1; custom_arg alias for $2; case_id alias for $3; workflow_key alias for $4; transition_key alias for $5; place_key alias for $6; direction alias for $7; begin if callback is null then -- null guard evaluates to true return ''t''; else if callback = ''#'' then return ''f''; else execute ''begin :1 := '' || callback || ''(:2, :3, :4, :5, :6, :7); end;'' using out v_guard_happy_p, in case_id, in workflow_key, in transition_key, in place_key, in direction, in custom_arg; return v_guard_happy_p; end if; end if; end;' language 'plpgsql'; -- procedure execute_transition_callback create function workflow_case__execute_transition_callback (varchar,varchar,number,varchar) returns integer as ' declare callback alias for $1; custom_arg alias for $2; case_id alias for $3; transition_key alias for $4; begin if callback is not null then execute ''begin ''||callback || ''(:1, :2, :3); end;'' using in case_id, in transition_key, in custom_arg; end if; return 0; end;' language 'plpgsql'; -- function execute_time_callback create function workflow_case__execute_time_callback (varchar,varchar,number,varchar) returns integer as ' declare callback alias for $1; custom_arg alias for $2; case_id alias for $3; transition_key alias for $4; begin if callback is null then raise_application_error(-20000, ''There''''s no time_callback function for the timed transition '''''' || transition_key || ''''''''); end if; execute ''begin :1 := '' || callback || ''(:2, :3, :4); end;'' using out v_trigger_time, in case_id, in transition_key, in custom_arg; return v_trigger_time; end;' language 'plpgsql'; -- function get_task_deadline create function workflow_case__get_task_deadline (varchar,varchar,varchar,number,varchar) returns integer as ' declare callback alias for $1; custom_arg alias for $2; attribute_name alias for $3; case_id alias for $4; transition_key alias for $5; begin /* * 1. or if there''s a row in wf_case_deadlines, we use that * 2. if there is a callback, we execute that * 3. otherwise, if there is an attribute, we use that */ /* wf_case_deadlines */ open case_deadline_cur; fetch case_deadline_cur into v_deadline; if case_deadline_cur%NOTFOUND then if callback is not null then /* callback */ execute ''begin :1 := '' || callback || ''(:2, :3, :4); end;'' using out v_deadline, in case_id, in transition_key, in custom_arg; elsif attribute_name is not null then /* attribute */ v_deadline := acs_object.get_attribute( object_id_in => get_task_deadline__case_id, attribute_name_in => get_task_deadline__attribute_name ); else v_deadline := null; end if; end if; return v_deadline; end;' language 'plpgsql'; -- function execute_hold_timeout_callback create function workflow_case__execute_hold_timeout_callback (varchar,varchar,number,varchar) returns integer as ' declare callback alias for $1; custom_arg alias for $2; case_id alias for $3; transition_key alias for $4; begin if callback is null then return null; end if; execute ''begin :1 := '' || callback || ''(:2, :3, :4); end;'' using out v_hold_timeout, in case_id, in transition_key, in custom_arg; return v_hold_timeout; end;' language 'plpgsql'; -- procedure execute_unassigned_callback create function workflow_case__execute_unassigned_callback (varchar,number,varchar) returns integer as ' declare callback alias for $1; task_id alias for $2; custom_arg alias for $3; begin if callback is not null then execute ''begin '' || callback || ''(:1, :2); end;'' using in task_id, in custom_arg; end if; return 0; end;' language 'plpgsql'; -- procedure set_task_assignments create function workflow_case__set_task_assignments (number,varchar,varchar) returns integer as ' declare task_id alias for $1; callback alias for $2; custom_arg alias for $3; begin /* Find out who to assign the given task to. * * 1. See if there are rows in wf_case_assignments. * 2. If not, and a callback is defined, execute that. * 3. Otherwise, grab the assignment from the workflow context. * * (We used to use the callback first, but that makes * reassignment of tasks difficult.) */ v_done_p := ''f''; for case_assignment_rec in case_assignments loop v_done_p := ''t''; add_task_assignment ( task_id => task_id, party_id => case_assignment_rec.party_id ); end loop; if v_done_p != ''t'' then if callback is not null then execute ''begin ''|| set_task_assignments__callback || ''(:1, :2); end;'' using in set_task_assignments__task_id, in set_task_assignments__custom_arg; else for context_assignment_rec in context_assignments loop add_task_assignment ( task_id => task_id, party_id => context_assignment_rec.party_id ); end loop; end if; end if; return 0; end;' language 'plpgsql'; -- procedure add_token create function workflow_case__add_token (number,varchar,number) returns integer as ' declare case_id alias for $1; place_key alias for $2; journal_id alias for $3; begin select wf_token_id_seq.nextval into v_token_id from dual; select workflow_key into v_workflow_key from wf_cases c where c.case_id = add_token__case_id; insert into wf_tokens (token_id, case_id, workflow_key, place_key, state, produced_journal_id) values (v_token_id, add_token__case_id, v_workflow_key, add_token__place_key, ''free'', add_token__journal_id); return 0; end;' language 'plpgsql'; -- procedure lock_token create function workflow_case__lock_token (number,varchar,number,number) returns integer as ' declare case_id alias for $1; place_key alias for $2; journal_id alias for $3; task_id alias for $4; begin update wf_tokens set state = ''locked'', locked_task_id = lock_token__task_id, locked_date = sysdate, locked_journal_id = lock_token__journal_id where case_id = lock_token__case_id and place_key = lock_token__place_key and state = ''free'' and rownum = 1; return 0; end;' language 'plpgsql'; -- procedure release_token create function workflow_case__release_token (number,number) returns integer as ' declare task_id alias for $1; journal_id alias for $2; cursor token_cur select token_id, case_id, place_key from wf_tokens where state and locked_task_id begin /* Add a new token for each released one */ for token_rec in token_cur loop add_token( case_id => token_rec.case_id, place_key => token_rec.place_key, journal_id => release_token__journal_id ); end loop; /* Mark the released ones canceled */ update wf_tokens set state = ''canceled'', canceled_date = sysdate, canceled_journal_id = release_token__journal_id where state = ''locked'' and locked_task_id = release_token__task_id; return 0; end;' language 'plpgsql'; -- procedure consume_token create function workflow_case__consume_token (number,varchar,number,number) returns integer as ' declare case_id alias for $1; place_key alias for $2; journal_id alias for $3; task_id alias for $4; begin if task_id is null then update wf_tokens set state = ''consumed'', consumed_date = sysdate, consumed_journal_id = consume_token__journal_id where case_id = consume_token__case_id and place_key = consume_token__place_key and state = ''free'' and rownum = 1; else update wf_tokens set state = ''consumed'', consumed_date = sysdate, consumed_journal_id = consume_token__journal_id where case_id = consume_token__case_id and place_key = consume_token__place_key and state = ''locked'' and locked_task_id = consume_token__task_id; end if; return 0; end;' language 'plpgsql'; -- procedure sweep_automatic_transitions create function workflow_case__sweep_automatic_transitions (number,number) returns integer as ' declare case_id alias for $1; journal_id alias for $2; cursor enabled_automatic_transitions select task_id from wf_tasks where tr.workflow_key and tr.transition_key and tr.trigger_type and ta.state and ta.case_id v_done_p char(1); v_finished_p char(1); begin enable_transitions(case_id => sweep_automatic_transitions__case_id); loop v_done_p := ''t''; v_finished_p := finished_p( case_id => sweep_automatic_transitions__case_id, journal_id => sweep_automatic_transitions__journal_id); if v_finished_p = ''f'' then for task_rec in enabled_automatic_transitions loop fire_transition_internal( task_id => task_rec.task_id, journal_id => sweep_automatic_transitions__journal_id ); v_done_p := ''f''; end loop; enable_transitions(case_id => sweep_automatic_transitions__case_id); end if; exit when v_done_p = ''t''; end loop; return 0; end;' language 'plpgsql'; -- function finished_p create function workflow_case__finished_p (number,number) returns integer as ' declare case_id alias for $1; journal_id alias for $2; begin select state into v_case_state from wf_cases where case_id = finished_p__case_id; if v_case_state = ''finished'' then return ''t''; else /* Let''s see if the case is actually finished, but just not marked so */ select decode(count(*),0,0,1) into v_num_rows from wf_tokens where case_id = finished_p__case_id and place_key = ''end''; if v_num_rows = 0 then return ''f''; else /* There''s a token in the end place. * Count the total number of tokens to make sure the wf is well-constructed. */ select decode(count(*),0,0,1,1,2) into v_num_rows from wf_tokens where case_id = finished_p__case_id and state in (''free'', ''locked''); if v_num_rows > 1 then raise_application_error(-20000, ''The workflow net is misconstructed: Some parallel executions have not finished.''); end if; /* Consume that token */ select token_id into v_token_id from wf_tokens where case_id = finished_p__case_id and state in (''free'', ''locked''); consume_token( case_id => finished_p__case_id, place_key => ''end'', journal_id => finished_p__journal_id ); update wf_cases set state = ''finished'' where case_id = finished_p__case_id; /* Add an extra entry to the journal */ v_journal_id := journal_entry.new( object_id => finished_p__case_id, action => ''case finish'', action_pretty => ''Case finished'' ); return ''t''; end if; end if; end;' language 'plpgsql'; -- procedure notify_assignee create function workflow_case__notify_assignee (integer,integer,<=>,varchar) returns integer as ' declare task_id alias for $1; user_id alias for $2; callback alias for $3; custom_arg alias for $4; v_deadline_pretty varchar(400); v_object_name varchar(4000); v_transition_key wf_transitions.transition_key%TYPE; v_transition_name wf_transitions.transition_name%TYPE; v_party_from parties.party_id%TYPE; v_party_to parties.party_id%TYPE; v_subject varchar(4000); v_body varchar(4000); v_request_id integer; begin select to_char(ta.deadline,''Mon fmDDfm, YYYY HH24:MI:SS''), acs_object.name(c.object_id), tr.transition_key, tr.transition_name into v_deadline_pretty, v_object_name, v_transition_key, v_transition_name from wf_tasks ta, wf_transitions tr, wf_cases c where ta.task_id = notify_assignee__task_id and c.case_id = ta.case_id and tr.workflow_key = c.workflow_key and tr.transition_key = ta.transition_key; /* Default values */ v_party_from := -1; /* This default value should probably be pulled from somewhere */ v_subject := ''Assignment: ''||v_transition_name||'' ''||v_object_name; v_body := ''You have been assigned to a task. ''||'' Task : ''||v_transition_name||'' Object : ''||v_object_name||'' ''; if v_deadline_pretty != '''' then v_body := v_body ||''Deadline: ''||v_deadline_pretty||'' ''; end if; /* We''d like to add a URL to go visit, but how do we get that URL? */ /* The notifications should really be sent from the application server layer, not from the database */ if notify_assignee__callback is not null then execute ''begin ''||notify_assignee__callback||''(:1, :2, :3, :4, :5, :6); end;'' using in notify_assignee__task_id, in notify_assignee__custom_arg, in notify_assignee__user_id, in out v_party_from, in out v_subject, in out v_body; end if; v_request_id := nt.post_request ( party_from => v_party_from, party_to => notify_assignee__user_id, expand_group => ''f'' , subject => v_subject, message => v_body, max_retries => 3 ); return 0; end;' language 'plpgsql'; -- procedure enable_transitions create function workflow_case__enable_transitions (number) returns integer as ' declare case_id alias for $1; cursor tasks_to_create select transition_key, transition_name, trigger_type, enable_callback, enable_custom_arg, assignment_callback, assignment_custom_arg, time_callback, time_custom_arg, deadline_callback, deadline_custom_arg, deadline_attribute_name, notification_callback, notification_custom_arg, unassigned_callback, unassigned_custom_arg, estimated_minutes from wf_enabled_transitions where et.case_id and not where case_id and transition_key and state v_task_id number; v_workflow_key varchar(100); v_trigger_time timestamp; v_deadline_date timestamp; v_party_from integer; v_subject varchar(500); v_body varchar(4000); v_num_assigned number; request_id nt_requests.request_id%TYPE; cursor assignees_cur select distinct from wf_task_assignments party_approved_member_map m, users u where tasgn.task_id and m.party_id and u.user_id begin select workflow_key into v_workflow_key from wf_cases where case_id = enable_transitions__case_id; /* we mark tasks overridden if they were once enabled, but are no longer so */ update wf_tasks set state = ''overridden'', overridden_date = sysdate where case_id = enable_transitions__case_id and state = ''enabled'' and transition_key not in (select transition_key from wf_enabled_transitions where case_id = enable_transitions__case_id); /* insert a task for the transitions that are enabled but have no task row */ for trans_rec in tasks_to_create loop v_trigger_time := null; v_deadline_date := null; if trans_rec.trigger_type = ''user'' then v_deadline_date := get_task_deadline( callback => trans_rec.deadline_callback, custom_arg => trans_rec.deadline_custom_arg, attribute_name => trans_rec.deadline_attribute_name, case_id => enable_transitions__case_id, transition_key => trans_rec.transition_key ); elsif trans_rec.trigger_type = ''time'' then v_trigger_time := execute_time_callback(trans_rec.time_callback, trans_rec.time_custom_arg, enable_transitions__case_id, trans_rec.transition_key); end if; /* we''re ready to insert the row */ select wf_task_id_seq.nextval into v_task_id from dual; insert into wf_tasks ( task_id, case_id, workflow_key, transition_key, deadline, trigger_time, estimated_minutes ) values ( v_task_id, enable_transitions__case_id, v_workflow_key, trans_rec.transition_key, v_deadline_date, v_trigger_time, trans_rec.estimated_minutes ); set_task_assignments( task_id => v_task_id, callback => trans_rec.assignment_callback, custom_arg => trans_rec.assignment_custom_arg ); /* Execute the transition enabled callback */ execute_transition_callback( callback => trans_rec.enable_callback, custom_arg => trans_rec.enable_custom_arg, case_id => enable_transitions__case_id, transition_key => trans_rec.transition_key ); select count(*) into v_num_assigned from wf_task_assignments where task_id = v_task_id; if v_num_assigned = 0 then execute_unassigned_callback ( callback => trans_rec.unassigned_callback, task_id => v_task_id, custom_arg => trans_rec.unassigned_custom_arg ); end if; end loop; return 0; end;' language 'plpgsql'; -- procedure fire_transition_internal create function workflow_case__fire_transition_internal (number,number) returns integer as ' declare task_id alias for $1; journal_id alias for $2; v_case_id number; v_state varchar(40); v_transition_key varchar(100); v_workflow_key varchar(100); v_place_key varchar(100); v_direction varchar(3); v_guard_happy_p char(1); v_fire_callback varchar(100); v_fire_custom_arg varchar(4000); v_found_happy_guard char(1); v_locked_task_id number; begin select t.case_id, t.state, t.workflow_key, t.transition_key, ti.fire_callback, ti.fire_custom_arg into v_case_id, v_state, v_workflow_key, v_transition_key, v_fire_callback, v_fire_custom_arg from wf_tasks t, wf_cases c, wf_transition_info ti where t.task_id = fire_transition_internal__task_id and c.case_id = t.case_id and ti.context_key = c.context_key and ti.workflow_key = c.workflow_key and ti.transition_key = t.transition_key; /* Check that the state is either started or enabled */ if v_state = ''enabled'' then v_locked_task_id := null; elsif v_state = ''started'' then v_locked_task_id := fire_transition_internal__task_id; else raise_application_error(-20000, ''Can''''t fire the transition if it''''s not in state enabled or started''); end if; /* Mark the task finished */ update wf_tasks set state = ''finished'', finished_date = sysdate where task_id = fire_transition_internal__task_id; /* Consume the tokens */ for place_rec in input_places(v_workflow_key, v_transition_key) loop consume_token( case_id => v_case_id, place_key => place_rec.place_key, journal_id => fire_transition_internal__journal_id, task_id => v_locked_task_id ); end loop; /* Spit out new tokens in the output places */ v_found_happy_guard := ''f''; for place_rec in output_places(v_workflow_key, v_transition_key) loop v_place_key := place_rec.place_key; v_direction := place_rec.direction; v_guard_happy_p := evaluate_guard( callback => place_rec.guard_callback, custom_arg => place_rec.guard_custom_arg, case_id => v_case_id, workflow_key => v_workflow_key, transition_key => v_transition_key, place_key => v_place_key, direction => v_direction ); if v_guard_happy_p = ''t'' then v_found_happy_guard := ''t''; add_token( case_id => v_case_id, place_key => place_rec.place_key, journal_id => fire_transition_internal__journal_id ); end if; end loop; /* If we didn''t find any happy guards, look for arcs with the special hash (#) guard */ if v_found_happy_guard = ''f'' then for place_rec in ( select place_key from wf_transition_places tp where tp.workflow_key = v_workflow_key and tp.transition_key = v_transition_key and tp.direction = ''out'' and tp.guard_callback = ''#'') loop add_token( case_id => v_case_id, place_key => place_rec.place_key, journal_id => fire_transition_internal__journal_id ); end loop; end if; /* Execute the transition fire callback */ execute_transition_callback( callback => v_fire_callback, custom_arg => v_fire_custom_arg, case_id => v_case_id, transition_key => v_transition_key ); return 0; end;' language 'plpgsql'; -- procedure ensure_task_in_state create function workflow_case__ensure_task_in_state (number,varchar) returns integer as ' declare task_id alias for $1; state alias for $2; v_count number; begin select decode(count(*),0,0,1) into v_count from wf_tasks where task_id = ensure_task_in_state__task_id and state = ensure_task_in_state__state; if v_count != 1 then raise_application_error(-20000, ''The task ''|| ensure_task_in_state__task_id || '' is not in state '''''' || ensure_task_in_state__state || ''''''''); end if; return 0; end;' language 'plpgsql'; -- procedure start_task create function workflow_case__start_task (number,number,number) returns integer as ' declare task_id alias for $1; user_id alias for $2; journal_id alias for $3; v_case_id number; v_workflow_key wf_workflows.workflow_key%TYPE; v_transition_key varchar(100); v_hold_timeout_callback varchar(100); v_hold_timeout_custom_arg varchar(4000); v_hold_timeout timestamp; begin ensure_task_in_state(task_id => start_task__task_id, state => ''enabled''); select t.case_id, t.workflow_key, t.transition_key, ti.hold_timeout_callback, ti.hold_timeout_custom_arg into v_case_id, v_workflow_key, v_transition_key, v_hold_timeout_callback, v_hold_timeout_custom_arg from wf_tasks t, wf_cases c, wf_transition_info ti where t.task_id = start_task__task_id and c.case_id = t.case_id and ti.context_key = c.context_key and ti.workflow_key = t.workflow_key and ti.transition_key = t.transition_key; v_hold_timeout := execute_hold_timeout_callback(v_hold_timeout_callback, v_hold_timeout_custom_arg, v_case_id, v_transition_key); /* Mark it started */ update wf_tasks set state = ''started'', started_date = sysdate, holding_user = start_task__user_id, hold_timeout = v_hold_timeout where task_id = start_task__task_id; /* Reserve one token from each input place */ for place_rec in input_places(v_workflow_key,v_transition_key) loop lock_token( case_id => v_case_id, place_key => place_rec.place_key, journal_id => start_task__journal_id, task_id => start_task__task_id ); end loop; return 0; end;' language 'plpgsql'; -- procedure cancel_task create function workflow_case__cancel_task (number,number) returns integer as ' declare task_id alias for $1; journal_id alias for $2; begin ensure_task_in_state(task_id => cancel_task__task_id, state => ''started''); select case_id into v_case_id from wf_tasks where task_id = cancel_task__task_id; /* Mark the task canceled */ update wf_tasks set state = ''canceled'', canceled_date = sysdate where task_id = cancel_task__task_id; /* Release our reserved tokens */ release_token( task_id => cancel_task__task_id, journal_id => cancel_task__journal_id ); /* The workflow state has now changed, so we must run this */ sweep_automatic_transitions( case_id => v_case_id, journal_id => cancel_task__journal_id ); return 0; end;' language 'plpgsql'; -- procedure finish_task create function workflow_case__finish_task (number,number) returns integer as ' declare task_id alias for $1; journal_id alias for $2; begin select case_id into v_case_id from wf_tasks where task_id = finish_task__task_id; fire_transition_internal( task_id => finish_task__task_id, journal_id => finish_task__journal_id ); sweep_automatic_transitions( case_id => v_case_id, journal_id => finish_task__journal_id ); return 0; end;' language 'plpgsql'; -- show errors -- -- schedule processing of the notification queue -- create function inline_0 () returns integer as ' begin select nt__schedule_process (1,'localhost',25); NONE, NONE, 25 ); return 0; end;' language 'plpgsql'; select inline_0 (); drop function inline_0 ();