lib/openwfe/expressions/flowexpression.rb in ruote-0.9.19 vs lib/openwfe/expressions/flowexpression.rb in ruote-0.9.20
- old
+ new
@@ -1,50 +1,36 @@
-#
#--
-# Copyright (c) 2006-2008, John Mettraux, OpenWFE.org
-# All rights reserved.
+# Copyright (c) 2006-2009, John Mettraux, jmettraux@gmail.com
#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
#
-# . Redistributions of source code must retain the above copyright notice, this
-# list of conditions and the following disclaimer.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
#
-# . 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.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
#
-# . 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.
+# Made in Japan.
#++
-#
-#
-# "made in Japan"
-#
-# John Mettraux at openwfe.org
-#
-
require 'openwfe/utils'
require 'openwfe/logging'
require 'openwfe/contextual'
require 'openwfe/rudefinitions'
require 'openwfe/util/ometa'
require 'openwfe/util/dollar'
+require 'openwfe/expressions/expression_tree'
module OpenWFE
#
@@ -58,12 +44,15 @@
#
# The base class for all OpenWFE flow expression classes.
# It gathers all the methods for attributes and variable lookup.
#
class FlowExpression < ObjectWithMeta
- include Contextual, Logging, OwfeServiceLocator
+ include Contextual
+ include Logging
+ include OwfeServiceLocator
+
#
# The 'flow expression id' the unique identifier within a
# workflow instance for this expression instance.
#
attr_accessor :fei
@@ -133,20 +122,10 @@
#
attr_accessor :updated_at
#
- # The classical no-params constructors.
- #
- def initialize
-
- super
- #
- # very necessary as this class includes the MonitorMixin
- end
-
- #
# Builds a new instance of an expression
#
def self.new_exp (fei, parent_id, env_id, app_context, attributes)
e = self.new
@@ -180,44 +159,59 @@
# this default implementation immediately replies to the
# parent expression
#
def reply (workitem)
- reply_to_parent workitem
+ reply_to_parent(workitem)
end
#
# Triggers the reply to the parent expression (of course, via the
# expression pool).
# Expressions do call this method when their job is done and the flow
# should resume without them.
#
def reply_to_parent (workitem)
- get_expression_pool.reply_to_parent self, workitem
+ get_expression_pool.reply_to_parent(self, workitem)
end
#
- # a default implementation for cancel :
- # cancels all the children
+ # A default implementation for cancel :
+ # triggers any registered 'on_cancel' and then cancels all the children
+ #
# Attempts to return an InFlowWorkItem
#
def cancel
- return nil unless @children
+ trigger_on_cancel
- inflowitem = nil
+ (@children || []).inject(nil) do |workitem, child|
- @children.each do |child|
+ #wi = child.is_a?(String) ? nil : get_expression_pool.cancel(child)
+ wi = get_expression_pool.cancel(child)
+ workitem ||= wi
+ end
+ end
- next if child.is_a?(String)
+ #
+ # triggers the on_cancel attribute of the expression, if any, and forgets
+ # it...
+ #
+ # makes sure to pass a copy of the cancelled process's variables to the
+ # on_cancel process/participant if any
+ #
+ def trigger_on_cancel
- i = get_expression_pool.cancel child
- inflowitem ||= i
- end
+ on_cancel = (self.attributes || {})['on_cancel'] || return
- inflowitem
+ on_cancel, workitem = on_cancel
+
+ template = lookup_variable(on_cancel) || [ on_cancel, {}, [] ]
+
+ get_expression_pool.launch_subprocess(
+ self,template, true, workitem, get_environment.lookup_all_variables)
end
#
# some convenience methods
@@ -225,11 +219,11 @@
# Returns the parent expression (not as a FlowExpressionId but directly
# as the FlowExpression instance it is).
#
def get_parent
- get_expression_pool.fetch_expression @parent_id
+ get_expression_pool.fetch_expression(@parent_id)
end
#
# Stores itself in the expression pool.
# It's very important for expressions in persisted context to save
@@ -237,14 +231,14 @@
# Else this information would be lost at engine restart or
# simply if the expression got swapped out of memory and reloaded later.
#
def store_itself
- ldebug { "store_itself() for #{@fei.to_debug_s}" }
+ #ldebug { "store_itself() for #{@fei.to_debug_s}" }
#ldebug { "store_itself() \n#{OpenWFE::caller_to_s(0, 6)}" }
- get_expression_pool.update self
+ get_expression_pool.update(self)
end
#
# Returns the environment instance this expression uses.
# An environment is a container (a scope) for variables in the process
@@ -270,48 +264,35 @@
#
# Just fetches the environment for this expression.
#
def fetch_environment
- get_expression_pool.fetch_expression @environment_id
+ get_expression_pool.fetch_expression(@environment_id)
end
#
# Returns true if the expression's environment was generated
# for itself (usually DefineExpression do have such envs)
#
def owns_its_environment?
- #ldebug do
- # "owns_its_environment?()\n" +
- # " #{@fei.to_debug_s}\n" +
- # " #{@environment_id.to_debug_s}"
- #end
-
return false if not @environment_id
ei = @fei.dup
vi = @environment_id.dup
- ei.expression_name = "neutral"
- vi.expression_name = "neutral"
+ ei.expression_name = 'neutral'
+ vi.expression_name = 'neutral'
- #ldebug do
- # "owns_its_environment?()\n"+
- # " exp #{ei.to_debug_s}\n"+
- # " env #{vi.to_debug_s}"
- #end
-
(ei == vi)
end
#
# Returns true if this expression belongs to a paused flow
#
def paused?
- #(lookup_variable(VAR_PAUSED) == true)
get_expression_pool.is_paused?(self)
end
#
# Sets a variable in the current environment. Is usually
@@ -320,17 +301,11 @@
# The variable name may be prefixed by / to indicate process level scope
# or by // to indicate engine level (global) scope.
#
def set_variable (varname, value)
- env, var = lookup_environment varname
-
- ldebug do
- "set_variable() '#{varname}' to '#{value}' " +
- "in #{env.fei.to_debug_s}"
- end
-
+ env, var = lookup_environment(varname)
env[var] = value
end
alias :sv :set_variable
@@ -342,26 +317,39 @@
# The variable name may be prefixed by / to indicate process level scope
# or by // to indicate engine level (global) scope.
#
def lookup_variable (varname)
- env, var = lookup_environment varname
+ env, var = lookup_environment(varname)
env[var]
end
alias :lv :lookup_variable
#
+ # Returns a stack of variable values, from here down to the engine
+ # environment.
+ #
+ # A stack is simply an array whose first value is the local value and
+ # the last value, the value registered in the engine env (if any is
+ # registered there).
+ #
+ def lookup_variable_stack (varname)
+
+ get_environment.lookup_variable_stack(varname)
+ end
+
+ #
# Unsets a variable in the current environment.
#
# The variable name may be prefixed by / to indicate process level scope
# or by // to indicate engine level (global) scope.
#
def delete_variable (varname)
- env, var = lookup_environment varname
- env.delete var
+ env, var = lookup_environment(varname)
+ env.delete(var)
end
alias :unset_variable :delete_variable
#
@@ -385,15 +373,12 @@
default = options[:default]
escape = options[:escape]
tostring = options[:to_s]
- attname = OpenWFE::symbol_to_name(attname) \
- if attname.kind_of?(Symbol)
+ attname = OpenWFE.symbol_to_name(attname) if attname.kind_of?(Symbol)
- #ldebug { "lookup_attribute() '#{attname}' in #{@fei.to_debug_s}" }
-
text = @attributes[attname]
text = if text == nil
default
@@ -402,11 +387,11 @@
text
else
- OpenWFE::dosub text, self, workitem
+ OpenWFE.dosub(text, self, workitem)
end
text = text.to_s if text and tostring
text
@@ -415,11 +400,11 @@
#
# Returns the attribute value as a String (or nil if it's not found).
#
def lookup_string_attribute (attname, workitem, options={})
- result = lookup_attribute attname, workitem, options
+ result = lookup_attribute(attname, workitem, options)
result = result.to_s if result
result
end
#
@@ -427,33 +412,33 @@
# (and stripped).
# Returns nil if no such attribute was found.
#
def lookup_downcase_attribute (attname, workitem, options={})
- result = lookup_string_attribute attname, workitem, options
+ result = lookup_string_attribute(attname, workitem, options)
result = result.strip.downcase if result
result
end
#
# Returns the value of the attribute as a Symbol.
# Returns nil if there is no attribute under the given name.
#
def lookup_sym_attribute (attname, workitem, options={})
- result = lookup_downcase_attribute attname, workitem, options
+ result = lookup_downcase_attribute(attname, workitem, options)
result = result.to_sym if result
result
end
#
# A convenience method for looking up a boolean value.
# It's ok to pass a Symbol for the attribute name.
#
def lookup_boolean_attribute (attname, workitem, default=false)
- result = lookup_downcase_attribute attname, workitem
+ result = lookup_downcase_attribute(attname, workitem)
return default if result == nil
(result == 'true')
end
@@ -462,17 +447,17 @@
# (probably a string) will split it (comma) and return it
# (each element trimmed).
#
def lookup_array_attribute (attname, workitem, options={})
- tostring = options.delete :to_s
+ tostring = options.delete(:to_s)
- v = lookup_attribute attname, workitem, options
+ v = lookup_attribute(attname, workitem, options)
return nil unless v
- v = v.to_s.split(",").collect { |e| e.strip } \
+ v = v.to_s.split(',').collect { |e| e.strip } \
unless v.is_a?(Array)
v = v.collect { |e| e.to_s } \
if tostring
@@ -483,12 +468,11 @@
# Returns true if the expression has the given attribute.
# The attname parameter can be a String or a Symbol.
#
def has_attribute (attname)
- attname = OpenWFE::symbol_to_name(attname) \
- if attname.kind_of?(Symbol)
+ attname = OpenWFE::symbol_to_name(attname) if attname.is_a?(Symbol)
(@attributes[attname] != nil)
end
#
@@ -518,12 +502,10 @@
#
# creates a new environment just for this expression
#
def new_environment (initial_vars=nil)
- ldebug { "new_environment() for #{@fei.to_debug_s}" }
-
@environment_id = @fei.dup
@environment_id.expression_name = EN_ENVIRONMENT
parent_fei = nil
parent = nil
@@ -534,20 +516,18 @@
parent_fei = parent.environment_id if parent
env = Environment.new_env(
@environment_id, parent_fei, nil, @application_context, nil)
- env.variables.merge! initial_vars if initial_vars
+ env.variables.merge!(initial_vars) if initial_vars
env[@fei.wfname] = self.raw_representation \
if (not @parent_id) and (self.is_a?(RawExpression))
#
# keeping track of the raw representation
# of the top expression (for top recursion)
- ldebug { "new_environment() is #{env.fei.to_debug_s}" }
-
env.store_itself
env
end
@@ -574,29 +554,26 @@
def clean_children
return unless @children
@children.each do |child_fei|
- get_expression_pool.remove(child_fei) \
- if child_fei.kind_of?(FlowExpressionId)
+ #next unless child.is_a?(FlowExpressionId)
+ get_expression_pool.remove(child_fei)
end
end
#
# Removes a child from the expression children list.
#
def remove_child (child_fei)
- #fei = @children.delete child_fei
- #store_itself if fei
+ i = @children.index(child_fei)
- i = @children.index child_fei
-
return unless i
- @children.delete_at i
- raw_children.delete_at i
+ @children.delete_at(i)
+ raw_children.delete_at(i)
@raw_rep_updated = true
store_itself
end
@@ -607,55 +584,23 @@
def get_binding
binding()
end
- #--
- # Used like the classical Ruby synchronize, but as the OpenWFE
- # expression pool manages its own set of monitors, it's one of those
- # monitors that is used. But the synchronize code looks like the class
- # just included the MonitorMixin. No hassle.
#
- #def synchronize
- # #ldebug { "synchronize() ---in--- for #{@fei.to_debug_s}" }
- # get_expression_pool.get_monitor(@fei).synchronize do
- # yield
- # end
- # #ldebug { "synchronize() --out-- for #{@fei.to_debug_s}" }
- #end
- #++
-
- #
# Returns the text stored among the children
#
def fetch_text_content (workitem, escape=false)
- cs = children || raw_children
+ text = (children || raw_children).inject('') do |r, child|
- text = cs.inject("") do |r, child|
-
- if child.is_a?(RawExpression)
-
- r << child.fei.to_s
-
- elsif child.is_a?(FlowExpressionId)
-
- r << get_expression_pool\
- .fetch_expression(child).raw_representation.to_s
-
- else
-
- r << child.to_s
- end
+ r << child.to_s
end
- return nil if text == ""
+ return nil if text == ''
- text = OpenWFE::dosub(text, self, workitem) \
- unless escape
-
- text
+ escape ? text : OpenWFE::dosub(text, self, workitem)
end
#
# looks up for 'value', 'variable-value' and then for 'field-value'
# if necessary.
@@ -670,13 +615,12 @@
# looks up for 'ref', 'variable-ref' and then for 'field-ref'
# if necessary.
#
def lookup_ref (workitem, prefix='')
- ref = lookup_vf_attribute workitem, 'ref', :prefix => prefix
- return ref.to_s if ref
- nil
+ ref = lookup_vf_attribute(workitem, 'ref', :prefix => prefix)
+ ref ? ref.to_s : nil
end
#
# Looks up for value attributes like 'field-ref' or 'variable-value'
#
@@ -685,14 +629,13 @@
att_name = att_name.to_s
prefix = options[:prefix] || ''
prefix = prefix.to_s
- dash = (att_name.size > 0 and prefix.size > 0) ? "-" : ""
+ dash = (att_name.size > 0 and prefix.size > 0) ? '-' : ''
- v = lookup_attribute(
- "#{prefix}#{dash}#{att_name}", workitem, options)
+ v = lookup_attribute("#{prefix}#{dash}#{att_name}", workitem, options)
att_name = "-#{att_name}" if att_name.size > 0
prefix = "#{prefix}-" if prefix.size > 0
return v if v
@@ -709,14 +652,11 @@
f = lookup_attribute(
"#{prefix}field#{att_name}", workitem, options) ||
lookup_attribute(
"#{prefix}f#{att_name}", workitem, options)
- #return workitem.attributes[f] if f
- return workitem.attributes[f.to_s] if f
-
- nil
+ f ? workitem.attributes[f.to_s] : nil
end
#
# Since OpenWFEru 0.9.17, each expression keeps his @raw_representation
# this is a shortcut for exp.raw_representation[2]
@@ -724,40 +664,78 @@
def raw_children
@raw_representation[2]
end
- SUBIDMUTEX = Mutex.new
+ #
+ # Returns a list of children that are expressions (arrays)
+ #
+ def raw_expression_children
+ @raw_representation[2].select { |c| c.is_a?(Array) }
+ end
+
#
+ # Returns true if the current expression has no expression among its
+ # [raw] children.
+ #
+ def has_no_expression_child
+
+ (first_expression_child == nil)
+ end
+
+ #
+ # Returns the index of the first child that is an expression.
+ #
+ def first_expression_child
+
+ @raw_representation[2].find { |c| c.is_a?(Array) }
+ end
+
+ #
# Returns the next sub process id available (this counter
# is stored in the process environment under the key :next_sub_id)
#
def get_next_sub_id
- #env = get_environment
env = get_root_environment
c = nil
- #env.synchronize do
-
c = env.variables[:next_sub_id]
n = if c
c + 1
else
c = 0
1
end
env.variables[:next_sub_id] = n
env.store_itself
- #end
c
end
#
+ # Given a child index (in the raw_children list/array), applies that
+ # child.
+ #
+ # Does the bulk work of preparing the children and applying it (also
+ # cares about registering the child in the @children array).
+ #
+ def apply_child (child_index, workitem)
+
+ child_index, child = if child_index.is_a?(Integer)
+ [ child_index, raw_children[child_index] ]
+ else
+ [ raw_children.index(child_index), child_index ]
+ end
+
+ get_expression_pool.tlaunch_child(
+ self, child, child_index, workitem, :register_child => true)
+ end
+
+ #
# Some eye candy
#
def to_s
s = "* #{@fei.to_debug_s} [#{self.class.name}]"
@@ -795,11 +773,11 @@
exp_names
end
end
#
- # returns true if the expression class is a 'definition'.
+ # Returns true if the expression class is a 'definition'.
#
def self.is_definition?
false
end
def self.is_definition
@@ -807,47 +785,88 @@
true
end
end
#
- # returns true if the expression class 'uses a template'
- # (children will not immediately get expanded at 'parse' time)
+ # Returns true if the expression with the given fei is an ancestor
+ # of this expression.
#
- def self.uses_template?
- false
+ def descendant_of? (fei)
+
+ #p [ :d_of?, "#{@fei.wfid} #{@fei.expid}", "#{fei.wfid} #{fei.expid}" ]
+
+ return false if @parent_id == nil
+ return true if @parent_id == fei
+ return true if fei.ancestor_of?(@fei) # shortcut
+
+ get_expression_pool.fetch_expression(@parent_id).descendant_of?(fei)
end
- def self.uses_template
- meta_def :uses_template? do
- true
- end
+
+ def marshal_dump #:nodoc#
+ iv = instance_variables
+ iv.delete('@application_context')
+ iv.inject({}) { |h, vn| h[vn] = instance_variable_get(vn); h }
end
- protected
+ def marshal_load (s) #:nodoc#
+ s.each { |k, v| instance_variable_set(k, v) }
+ end
- #
- # If the varname starts with '//' will return the engine
- # environment and the truncated varname...
- # If the varname starts with '/' will return the root environment
- # for the current process instance and the truncated varname...
- #
- def lookup_environment (varname)
+ def to_yaml_properties #:nodoc#
+ l = super
+ l.delete('@application_context')
+ l
+ end
- if varname[0, 2] == '//'
- return [
- get_expression_pool.fetch_engine_environment,
- varname[2..-1]
- ]
- end
+ protected
- if varname[0, 1] == '/'
- return [
- get_environment.get_root_environment,
- varname[1..-1]
- ]
+ #
+ # Initializes the @children member array.
+ #
+ # Used by 'concurrence' for example.
+ #
+ def extract_children
+ i = 0
+ @children = []
+ raw_representation.last.each do |child|
+ if OpenWFE::ExpressionTree.is_not_a_node?(child)
+ @children << child
+ else
+ cname = child.first.intern
+ next if cname == :param
+ next if cname == :parameter
+ #next if cname == :description
+ cfei = @fei.dup
+ cfei.expression_name = child.first
+ cfei.expression_id = "#{cfei.expression_id}.#{i}"
+ efei = @environment_id
+ rawexp = RawExpression.new_raw(
+ cfei, @fei, efei, @application_context, OpenWFE::fulldup(child))
+ get_expression_pool.update(rawexp)
+ i += 1
+ @children << rawexp.fei
end
-
- [ get_environment, varname ]
end
+ end
+
+ #
+ # If the varname starts with '//' will return the engine
+ # environment and the truncated varname...
+ # If the varname starts with '/' will return the root environment
+ # for the current process instance and the truncated varname...
+ #
+ def lookup_environment (varname)
+
+ return [
+ get_expression_pool.fetch_engine_environment, varname[2..-1]
+ ] if varname[0, 2] == '//'
+
+ return [
+ get_environment.get_root_environment, varname[1..-1]
+ ] if varname[0, 1] == '/'
+
+ [ get_environment, varname ]
+ end
end
end