- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
Class ::xowf::Context
::xowf::Context create ... \
[ -all_roles (default "false") ] \
[ -current_state (default "[self]::initial") ] \
[ -default_definition (default "") ] \
[ -in_role in_role ] \
[ -object object ] \
[ -wf_container wf_container ] \
[ -workflow_definition workflow_definition ]
Defined in
Class Relations
::xotcl::Class create ::xowf::Context \ -superclass ::xotcl::ObjectMethods (to be applied on the object)
require (scripted)
xowf::Context require
- Testcases:
- create_workflow_with_instance, xowf
# # Make sure, the context object for workflow '$obj exists. The # flag "-new" can be used to make sure, a new and fresh context is # available. # #:log "START-require" # set ctx $obj-wfctx if {$new && [nsf::is object $ctx]} { $ctx destroy } if {![nsf::is object $ctx]} { set wfContextClass [$obj wf_property workflow_context_class [self]] regsub -all \r\n [$obj wf_property workflow_definition] \n workflow_definition $wfContextClass create $ctx -object $obj -workflow_definition $workflow_definition -destroy_on_cleanup $ctx initialize_context $obj } #:log "END-require ctx <$ctx>" return $ctxMethods (to be applied on instances)
all_roles (setter)
as_graph (scripted)
set dot "" set dot [::util::which dot] if {$dot eq ""} { return "<font color='red'>Program 'dot' is not available! No graph displayed.</font>" } set dotcode [:dotcode -current_state $current_state -visited $visited -dpi $dpi] set svg [util::inline_svg_from_dot -css [subst { svg g a:link {text-decoration: none;} div.inner svg {height:100%; overflow: visible; $style; margin: 0 auto;} }] $dotcode] return $svgauto_form_constraints (scripted)
if {${:wf_container} ne [self]} { return [${:wf_container} auto_form_constraints] } elseif {[info exists :auto_form_constraints]} { return ${:auto_form_constraints} } return ""auto_form_template (scripted)
if {${:wf_container} ne [self]} { return [${:wf_container} auto_form_template] } elseif {[info exists :auto_form_template]} { return ${:auto_form_template} } return ""autoname (scripted)
# # We want to distinguish between a set "autoname" and an # unspecified "autoname". Therefore, we do not want to use a # default in the WorkflowContainer # if {${:wf_container} ne [self]} { if {[${:wf_container} exists autoname]} { return [${:wf_container} autoname] } } elseif {[info exists :autoname]} { return ${:autoname} } return "f"check (scripted)
ns_log notice "--- check context" # Check minimal contents set o [:wf_definition_object initial] if {![nsf::is object $o] || ![$o istype State]} { return [list rc 1 errorMsg "No State 'initial' defined"] } # ease access to workflow constructs foreach s [:defined State] {set state([$s name]) $s} foreach a [:defined Action] {set action([$a name]) $a} foreach a [:defined Condition] {set condition([$a name]) $a} array set condition {else 1 true 1 default 1} # Check actions foreach a [:defined Action] { # Are some "next_states" undefined? foreach {cond value} [$a get_cond_values [$a next_state]] { if {$cond ne "" && ![info exists condition($cond)]} { return [list rc 1 errorMsg "Error in action [$a name]: no such condition '$cond' defined (valid: [lsort [array names condition]])"] } if {$value ne "" && ![info exists state($value)]} { return [list rc 1 errorMsg "Error in action [$a name]: no such state '$value' defined (valid: [lsort [array names state]])"] } } } foreach s [:defined State] { # Are some "actions" undefined? foreach {cond actions} [$s get_cond_values [$s actions]] { foreach a $actions { if {![info exists action($a)]} { return [list rc 1 errorMsg "Error in state [$s name]: no such action '$a' defined (valid: [lsort [array names action]])"] } } } if {[$s form_loader] eq "" && [$s form] ne ""} { set :forms([$s form]) 1 } } foreach p [:defined ::xowiki::formfield::FormField] { if {[$p exists parampage]} {set :parampages([$p set parampage]) 1} } #:msg "forms=[array names :forms], parampages=[array names :parampages] in-role [info exists :in_role] [array names :handled_roles]" if {![info exists :in_role]} { foreach role [array names :handled_roles] { set role_ctx [self]-$role if {[nsf::is object $role_ctx]} { set info [$role_ctx check] if {[dict get $info rc] == 1} { return $info } array set :forms [$role_ctx array get forms] array set :parampage [$role_ctx array get parampage] } } #:msg "forms=[array names :forms], parampages=[array names :parampages]" set page ${:object} $page references clear $page set __unresolved_object_type ::xowiki::Form foreach {type pages} [list wf_form [array names :forms] wf_parampage [array names :parampages]] { foreach p $pages { set form_info [:resolve_form_name -object $page $p] set l [::xowiki::Link new -volatile -lang en -page $page -type $type -name [dict get $form_info name] -item_id [dict get $form_info form_id]] # # The "render" method of the link does the optional fetch of # the names, and maintains the variable references of the # page object (similar to render). # set link_text [$l render] } } set references [$page references get resolved] #:log "-- link_text=$link_text// $references" if {[llength $references] > 0} { #:msg "updating references refs=$references" $page references_update [lsort -unique $references] $page set __extra_references $references $page references clear } if {[llength [$page references get unresolved]] > 0} { # # TODO: We should provide a link to create the missing # forms. Maybe we change unresolved_references to a list..., # or maybe we write these into the DB. # :msg -html t "Missing forms: [join [$page references get unresolved] {, }]" } } return [list rc 0]create_auto_form (scripted)
# # Create a form on the fly. The created form can be influenced by # "auto_form_template" and "auto_form_constraints". # set vars [dict keys [$object set instance_attributes]] set template [:auto_form_template] if {$template ne ""} { :log "USE autoform template" } elseif {[llength $vars] == 0} { #set template "AUTO form, no instance variables defined,<br>@_text@" set template "@_text@" } else { set template "@[join $vars @,@]@<br>@_text@" } #:log "USE auto-form template=$template, vars=$vars # IA=[$object set instance_attributes], # V=[$object info vars] auto [:autoname]" set package_id [$object package_id] return [::xowiki::Form new -package_id $package_id -parent_id [::$package_id folder_id] -name "Auto-Form" -anon_instances [:autoname] -form {} -text [list $template text/html] -form_constraints [:auto_form_constraints] -destroy_on_cleanup ]create_workflow_definition (scripted)
# # Validation: since for shared workflow definitions, the workflow # container is named after the revision, we need only a validation # of "xowf::include" content (external source content). So the, # container-specific workflow_definition is revision specific, and # it will never change. # # set :__workflow_definition [list $workflow_definition] # if {[catch {${:wf_container} contains " Class create Action -superclass ::xowf::Action Class create State -superclass ::xowf::State Class create Condition -superclass ::xowf::Condition Class create Property -superclass ::xowf::Property -set abstract 1 [:default_definition] $workflow_definition"} errorMsg]} { ns_log error "Error in workflow definition ([${:object} name]): $errorMsg\n$::errorInfo\n ===== default_definition: [:default_definition] \n ===== workflow_definition: $workflow_definition" :msg -html t "Error in workflow definition of [${:object} name]: [ns_quotehtml $errorMsg]" } # # Store state of xowf-depends in the container for later # comparison. # if {[info exists ::__xowf_depends]} { ${:wf_container} set __xowf_depends [set ::__xowf_depends] } if {${:all_roles}} { #:msg want.to.create=[array names :handled_roles] foreach role [array names :handled_roles] { Context create ${:wf_container}-$role -workflow_definition $workflow_definition -in_role $role -object ${:object} } }current_state (setter)
debug (scripted)
if {${:wf_container} ne [self]} { return [${:wf_container} debug] } elseif {[info exists :debug]} { return ${:debug} } return 0default_definition (setter)
default_load_form_id (scripted)
#:msg "resolving $form_name in state [:current_state] via default form loader" set form_id 0 if {$form_name ne ""} { set resolved [:resolve_form_name -object ${:object} $form_name] set form_id [dict get $resolved form_id] if {$form_id == 0} { ns_log warning "could not resolve '$form_name' for ${:object}: $resolved" } #:msg ".... object ${:object} ==> id = $form_id" } return $form_iddefined (scripted)
set result [list] foreach c [${:wf_container} info children] { if {[$c istype $what]} {lappend result $c} } return $resultdotcode (scripted)
set obj_id [namespace tail ${:object}] set dotcode [subst {digraph workflow_$obj_id \{ dpi = $dpi; node \[shape=doublecircle, margin=0.001, fontsize=8, fixedsize=1, width=0.4, style=filled\]; start; node \[shape=ellipse, fontname="Courier", color=lightblue2, style=filled, fixedsize=0, fontsize=10, margin=0.06\]; edge \[fontname="Courier", fontsize=9\]; }] foreach s [:defined State] { if {[$s name] eq $current_state} { set color ",color=orange" } elseif {[$s name] in $visited} { set color ",color=yellow" } else { set color "" } append dotcode " state_[$s name] \[label=\"[$s label]\"$color\];\n" } set initializeObj [:wf_definition_object initialize] if {[nsf::is object $initializeObj]} { append dotcode "start->state_initial \[label=\"[$initializeObj label]\"\];\n" } else { append dotcode "start->state_initial;\n" } set :condition_count 0 foreach s [:defined State] { foreach a [$s get_actions -set true] { set actionObj [:wf_definition_object $a] append dotcode [:draw_transition $s $actionObj ""] set drawn($actionObj) 1 } foreach role [$s set handled_roles] { set role_ctx [self]-$role #:msg exists?role=$role->[self]-$role->[nsf::is object ${role_ctx}] if {[nsf::is object ${role_ctx}::[$s name]]} { foreach a [${role_ctx}::[$s name] get_actions] { append dotcode [:draw_transition $s ${role_ctx}::$a "$role:"] } } } } # # State-safe actions might be called from every state. Draw the # arcs if not done yet. # foreach action [:defined Action] { if {[info exists drawn($action)]} {continue} if {[$action state_safe]} { foreach s [:defined State] { append dotcode [:draw_transition $s $action ""] } } } append dotcode "\}\n" return $dotcodedraw_arc (scripted)
if {$next_state eq ""} {set next_state $from_state} set key transition($from_state,$next_state,$action) if {[info exists :$key]} { return "" } set :$key 1 return " state_$from_state -> state_$next_state \[label=\"$label\"$style\];\n"draw_transition (scripted)
#:msg "[self args]" if {[$action state_safe]} { set arc_style {,style="dashed",penwidth=1,color=gray} } else { set arc_style "" } set cond_values [$action get_cond_values [$action next_state]] set result "" if {[llength $cond_values]>2} { # we have conditional values set c cond_[$from name]_[incr :condition_count] append arc_style {,style="setlinewidth(1)",penwidth=1,color=gray} append result " state_$c \[shape=diamond, fixedsize=1, width=0.2, height=0.2, fixedsize=1,style=solid,color=gray,label=\"\"\];\n" append result [:draw_arc [$from name] $c [$action name]-1 $role[$action label] ""] foreach {cond value} $cond_values { if {$cond ne ""} {set prefix "$cond"} {set prefix "else"} append result [:draw_arc $c $value [$action name] \[$prefix\] $arc_style] } } else { set prefix "" append result [:draw_arc [$from name] [lindex $cond_values 1] [$action name] $role$prefix[$action label] $arc_style] } return $resultflush_form_object (scripted)
unset -nocomplain :form_objforce_named_form (scripted)
# # By using this method in the "initialize" action, one can bypass # the state specific forms and force a form to the certain name # set form_id [:default_load_form_id $form_name] if {$form_id == 0} { ns_log warning "use_named_form: could not locate form $form_name" } else { if {![nsf::is object ::${form_id}]} { ::xo::db::CrClass get_instance_from_db -item_id ${form_id} } set :form_id $form_id }form (forward)
form_loader (forward)
form_object (scripted)
set parent_id [$object parent_id] # After this method is activated, the form object of the form of # the current state is created and the instance variable form_id # is set. # # Load the actual form only once for this context. We cache the # object name of the form in the context. # if {[info exists :form_obj]} { return ${:form_obj} } set package_id [$object package_id] # # We have to load the form, maybe via a form loader. If the # form_loader is set nonempty and the method exists, then use the # form loader instead of the plain lookup. In case the form_loader # fails, it is supposed to return 0. # set loader [:form_loader] #:msg form_loader=$loader # TODO why no procsearch instead of "info methods"? if {$loader eq "" || [:info methods $loader] eq ""} { set form_id [:default_load_form_id [${:current_state} form]] if {$form_id == 0} { :log "=== NO default_load_form_id state ${:current_state} form <[${:current_state} form]>" # # When no form was found by the form loader ($form_id == 0) we # create automatically a form. # set form_object [:create_auto_form $object] :log "=== autoform $form_object" } } else { #:msg "using custom form loader $loader for [:form]" set form_object [:$loader [:form]] } # # At this place, the variable "form_id" might contain an id # (integer) or an object, provided by the custom file loader. # #:msg form_id=$form_id if {![info exists form_object] && [string is integer -strict $form_id] && $form_id > 0 } { # just load the object conditionally if {![nsf::is object ::$form_id]} { ::xo::db::CrClass get_instance_from_db -item_id $form_id } set form_object ::$form_id #:msg form_object=$form_object } if {[$form_object istype "::xowiki::Form"]} { # # The item returned from the form loader was a form, # everything is fine. # #:msg form_object=$form_object-isForm } elseif {[$form_object istype "::xowiki::FormPage"]} { # # We got a FormPage. This FormPage might be a pseudo form (a # FormPage containing the property "form"). If not, add a "form" # property from the rendered content. # #:msg form_object=$form_object-pseudoForm-with-form=[$form_object property form] if {[$form_object property form] eq ""} { # # The FormPage contains no form, so try to provide one. We # obtain the content by rendering the page_content. In some # cases it might be more efficient to obtain the content # from property "_text", but this might lead to unexpected # cases where the formpage uses _text for partial # information. # set text [$form_object render_content] $form_object set_property -new 1 form "<form>$text</form>" #:msg "_text=[$form_object property _text]" } } elseif {[$form_object info class] eq "::xowiki::Page"} { # # The $form_object is in reality an xowiki Page, make it look # like a form (with form buttons). # set form_object [::xowiki::Form new -package_id $package_id -parent_id [::$package_id folder_id] -name "Auto-Form" -anon_instances [:autoname] -form "<form>[$form_object get_html_from_content [::$form_id text]]</form>" -text "" -form_constraints "" -destroy_on_cleanup ] } set :form_obj $form_object return $form_objectget_actions (scripted)
set actions [list] foreach action [${:current_state} get_actions] { lappend actions ${:wf_container}::$action } #:msg "for ${:current_state} actions '$actions" return $actionsget_current_state (scripted)
namespace tail ${:current_state}get_form_constraints (forward)
get_property (forward)
get_view_method (forward)
in_role (setter)
init (scripted)
array set :handled_roles {} # # Register the context for the associated object. This has to be # before the creation of the workflow definition, since this might # refer to the context. # ${:object} wf_context [self] # # Create or share workflow definition # :require_workflow_definition ${:workflow_definition}initialize_context (scripted)
#:log "START-initialize_context <$obj>" # # Keep the object in an instance variable. # set :object $obj # set the state to a well defined starting point set state [$obj state] if {$state eq ""} { set state "initial" #:log "===== resetting state of $obj to $state" } :set_current_state $state if {![nsf::is object ${:current_state}]} { if {$state eq "initial"} { ns_log warning "no state object ${:current_state}" } else { # # The state was probably deleted from the workflow definition, # but the workflow instance does still need it. We complain an # reset the state to "initial", which should be always present. # :log "===== Workflow instance [$obj name] is in an undefined state '$state', reset to initial" $obj msg "Workflow instance [$obj name] is in an undefined state '$state', reset to initial" :set_current_state initial } } # # In most cases, the package_id is initialized here already # set package_id [$obj package_id] #:log "... OBJECT $obj HAS $package_id /[info commands ::$package_id/]" if {[info commands ::$package_id] eq ""} { :log "... OBJECT $obj HAS $package_id, which is not initialized yet" xo::Package require $package_id } # # Set the embedded_context to the workflow context, # used e.g. by "behavior" of form-fields. # [::$package_id context] set embedded_context [self] set stateObj ${:current_state} catch {$stateObj eval [$stateObj eval_when_active]} if {[$obj istype ::xowiki::FormPage] && [$obj is_wf_instance]} { # # The workflow context may have the following variables: # - "debug" # - "policy" # - "autoname" # - "auto_form_constraints" # - "auto_form_template" # - "shared_definition" # if {[${:wf_container} exists debug] && [${:wf_container} set debug] > 0} { :show_debug_info $obj } if {[${:wf_container} exists policy]} { set policy [${:wf_container} set policy] if {![nsf::is object $policy]} { :msg "ignore non-existent policy '$policy'" } else { [$obj package_id] set policy $policy } } } #:log "END-initialize_context <$obj>\n\t context vars: [lsort [:info vars]]\n\tcontainer vars: [lsort [${:wf_container} info vars]]"object (setter)
object-specific (scripted)
#:log "=== legacy call <$code>" :uplevel [list ${:object} eval $code]property (forward)
require_workflow_definition (scripted)
# # We define the classes Action, State and Property either # - per workflow instance (sub-object of the FormPage) or # - shared based on the revision_id of the workflow definition. # # Per-instance definitions have the advantage of allowing # e.g. per-object mixins for workflow context definitions, but # this can be costly for complex workflow definitions, e.g. when # multiple workflow instances are created for a single workflow # definition in a request. # #:log START-CREATES-sharedWorkflowDefinition=$::xowf::sharedWorkflowDefinition if {$::xowf::sharedWorkflowDefinition} { if {[${:object} is_wf]} { set source_obj ${:object} } else { set source_obj [${:object} page_template] } set revision_id [$source_obj revision_id] if {$revision_id == 0} { # # We have no "revision_id", but we have to have an # "item_id". Therefore, get then "item_id" from the # "revision_id" via SQL function # content_item.get_live_revision. # set revision_id [::acs::dc call content_item get_live_revision -item_id [$source_obj item_id]] ns_log warning "xowf: tried to create a wf_container with revision_id 0 -> fixed to $revision_id" } set :wf_container ::xowf::$revision_id # # Validate workflow container: We cannot trust the shared # definition in case some xowf-include files were changed. When # we detect such situations, we delete the shared worklow # container, which will be recreated later. # if {[nsf::is object ${:wf_container}]} { set ok 1 #set ok [expr {$workflow_definition eq [${:wf_container} set __workflow_definition]}] if {[${:wf_container} exists __xowf_depends]} { set depends [${:wf_container} set __xowf_depends] foreach {fn mtime} $depends { if {[ad_file mtime $fn] ne $mtime} { set ok 0 break } } } if {!$ok} { ${:wf_container} destroy ns_log notice "xowf: invalidate container ${:wf_container}" } } if {![nsf::is object ${:wf_container}]} { # # We require an xotcl::Object, since the container needs the # method "contains" # #:log "=== create WorkflowContainer ${:wf_container}" WorkflowContainer create ${:wf_container} #:log "=== call create :create_workflow_definition" :create_workflow_definition $workflow_definition #:log "==== def\n$workflow_definition" #:log "==== wf_container children <[${:wf_container} info children]>" } # # This is for transitional code. For certain workflows in the # transition, we can define in the workflow whether or not is # shall be really shared by setting in the definition the # variable "shared_definition". # set use_shared_definition [${:wf_container} shared_definition] } else { set use_shared_definition 0 } if {$use_shared_definition == 0} { set :wf_container [self] :create_workflow_definition $workflow_definition #:log [:serialize] } else { # # Evaluate once per request the object-specific code of the # workflow. # if {[${:object} is_wf_instance]} { set os_code [${:wf_container} object-specific] if {$os_code ne ""} { #:log "=== object-specific ${:object} eval <$os_code>" ${:object} eval $os_code } } } #:log [:serialize] #:log END-CREATESresolve_form_name (scripted)
set package_id [$object package_id] set parent_id [$object parent_id] set item_info [::$package_id item_ref -normalize_name false -use_package_path 1 -use_site_wide_pages true -default_lang [$object lang] -parent_id $parent_id $name] #ns_log notice "*** resolve_form_name <$name> in $parent_id [$parent_id name] => $item_info" set item_id [dict get $item_info item_id] set form_name [dict get $item_info prefix]:[dict get $item_info stripped_name] return [list form_id $item_id name $form_name]set_current_state (scripted)
set :current_state ${:wf_container}::$valueset_new_property (forward)
set_property (forward)
show_debug_info (scripted)
set form [${:current_state} form] set view_method [${:current_state} view_method] set form_loader [${:current_state} form_loader] if {$form eq ""} {set form NONE} if {$view_method eq ""} {set view_method NONE} if {$form_loader eq ""} {set form_loader NONE} $obj debug_msg "State: [${:current_state} name], Form: $form, View method: $view_method, Form loader: $form_loader, Context class: [:info class]" #set conds [list] #foreach c [:defined Condition] { # lappend conds "[$c name] [$c]" #} #$obj debug_msg "Conditions: [join $conds {, }]" $obj debug_msg "Instance attributes: [list [$obj instance_attributes]]" foreach kind {State Action Condition} { $obj debug_msg "...${kind}s: [lsort [:defined $kind]]" }wf-specific (scripted)
if {[llength $args] > 0} { ns_log warning "wf-specific NOT SUPPORTED for non shared workflow " "${:object} [${:object} name]: $args" }wf_container (setter)
wf_definition_object (scripted)
return ${:wf_container}::$nameworkflow_definition (setter)
- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables