#
#--
# 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)
#
# 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