# #-- # Copyright (c) 2007, John Mettraux, OpenWFE.org # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # . Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # . Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # . Neither the name of the "OpenWFE" nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. #++ # # $Id: definitions.rb 2725 2006-06-02 13:26:32Z jmettraux $ # # # "made in Japan" # # John Mettraux at openwfe.org # require 'openwfe/expressions/condition' require 'openwfe/expressions/wtemplate' require 'openwfe/expressions/flowexpression' # # 'cursor' and 'loop' expressions # module OpenWFE A_COMMAND_FIELD = "command-field" F_COMMAND = "__cursor_command__" A_DISALLOW = "disallow" C_BACK = "back" C_SKIP = "skip" C_BREAK = "break" C_CANCEL = "cancel" C_REWIND = "rewind" C_CONTINUE = "continue" C_JUMP = "jump" A_STEP = "step" # # The 'cursor' is much like a sequence, but you can go # back and forth within it, as it reads the field '\_\_cursor_command__' (or # the field specified in the 'command-field' attribute) at each # transition (each time it's supposed to move from one child expression to # the next). # # cursor do # participant "alpha" # skip :step => "2" # participant "bravo" # participant "charly" # set :field => "__cursor_command__", value => "2" # participant "delta" # participant "echo" # skip 2 # participant "fox" # # # # in that cursor example, only the participants alpha, charly and # # echo will be handed a workitem # # (notice the last 'skip' with its light syntax) # # # end # # As you can see, you can directly set the value of the field # '\_\_cursor_command__' or use a CursorCommandExpression like 'skip' or # 'jump'. # class CursorExpression < WithTemplateExpression names :cursor attr_accessor \ :loop_id, :current_child_id, :current_child_fei # # apply / reply def apply (workitem) @loop_id = 0 @current_child_id = -1 clean_children_list() reply(workitem) end def reply (workitem) if @children.length < 1 # # well, currently, no infinite empty loop allowed # reply_to_parent(workitem) return end command_field = lookup_command_field workitem command, step = lookup_command command_field, workitem disallow_list = lookup_disallow workitem command = nil \ if disallow_list and disallow_list.include?(command) ldebug { "reply() command is '#{command} #{step}'" } if command == C_BREAK or command == C_CANCEL reply_to_parent workitem return end if command == C_REWIND or command == C_CONTINUE @current_child_id = 0 elsif command and command.match "^#{C_JUMP}" @current_child_id = step @current_child_id = 0 if @current_child_id < 0 @current_child_id = @children.length - 1 \ if @current_child_id >= @children.length else # C_SKIP or C_BACK @current_child_id = @current_child_id + step @current_child_id = 0 if @current_child_id < 0 if @current_child_id >= @children.length if not is_loop reply_to_parent(workitem) return end @loop_id += 1 @current_child_id = 0 end end workitem.attributes.delete(command_field) @current_child_fei = nil store_itself() @current_child_fei = @children[@current_child_id] # # launch the next child as a template get_expression_pool.launch_template( self, @loop_id, @current_child_fei, workitem, nil) end # # takes care of cancelling the current child if necessary # def cancel get_expression_pool.cancel(@current_child_fei) \ if @current_child_fei super end # # Returns false, the child class LoopExpression does return true. # def is_loop false end protected # # Makes sure that only flow expression are left in the cursor # children list (text and comment nodes get discarded). # def clean_children_list c = @children @children = [] c.each do |child| @children << child \ if child.kind_of?(OpenWFE::FlowExpressionId) end end # # Looks up the value in the command field. # def lookup_command_field (workitem) lookup_attribute(A_COMMAND_FIELD, workitem, F_COMMAND) end # # Returns the command and the step # def lookup_command (command_field, workitem) command = workitem.attributes[command_field] return [ nil, 1 ] unless command # # this corresponds to the "just one step forward" default command, step = command.strip.split step = if step step.to_i else 1 end step = -step if command == C_BACK [ command, step ] end # # Fetches the value of the 'disallow' cursor attribute. # def lookup_disallow (workitem) lookup_comma_list_attribute(A_DISALLOW, workitem) end end # # The 'loop' expression is like 'cursor' but it doesn't exit until # it's broken (with 'break' or 'cancel'). # class LoopExpression < CursorExpression names :loop def is_loop true end end # # This class implements the following expressions : back, break, # cancel, continue, jump, rewind, skip. # # They are generally used inside of a 'cursor' (CursorExpression) or # a 'loop' (LoopExpression), they can be used outside, but their result # (the value of the field '\_\_cursor_command__' will be used as soon as the # flow enters a cursor or a loop). # # In fact, this expression is only a nice wrapper that sets the # value of the field "\_\_cursor_command__" to its name ('back' for example) # plus to the 'step' attribute value. # # For example simply sets the value of the field # '\_\_cursor_command__' to 'skip 3'. # # (The field \_\_cursor_command__ is, by default, read and # obeyed by the 'cursor' expression). # # With Ruby process definitions, you can directly write : # # skip 2 # jump "0" # # instead of # # skip :step => "2" # jump :step => "0" # # Likewise, in an XML process definition, you can write # # 2 # # although that might still look lighter (it's longer though) : # # # # # About the command themselves : # # * back : will go back from the number of given steps, 1 by default # * break : will exit the cursor (or the loop) # * cancel : an alias for 'break' # * continue : will exit the cursor, if in a loop, will get back at step 0 # * jump : will move the cursor (or loop) to an absolute given position (count starts at 0) # * rewind : an alias for continue # * skip : skips the given number of steps # # # All those command support an 'if' attribute to restrict their execution : # # cursor do # go_to_shop # check_prices # _break :if => "${price} > ${f:current_cash}" # buy_stuff # end # class CursorCommandExpression < FlowExpression include ConditionMixin names :back, :skip, :continue, :break, :cancel, :rewind, :jump def apply (workitem) conditional = eval_condition(:if, workitem, :unless) # # for example : if conditional == nil or conditional command = @fei.expression_name step = lookup_attribute(A_STEP, workitem) step = fetch_text_content(workitem) unless step step = 1 unless step step = Integer(step) command = "#{command} #{step}" #if step != 1 workitem.attributes[F_COMMAND] = command end reply_to_parent workitem end end end