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